<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Precious Sholanke</title>
    <description>The latest articles on DEV Community by Precious Sholanke (@oyinda9).</description>
    <link>https://dev.to/oyinda9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962445%2Fd4152e30-3fd7-4619-91f3-93d90443bafa.jpeg</url>
      <title>DEV Community: Precious Sholanke</title>
      <link>https://dev.to/oyinda9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oyinda9"/>
    <language>en</language>
    <item>
      <title>SERIES: Building SaloonBook -a booking system is not a CRUD app</title>
      <dc:creator>Precious Sholanke</dc:creator>
      <pubDate>Mon, 01 Jun 2026 10:57:12 +0000</pubDate>
      <link>https://dev.to/oyinda9/series-building-saloonbook-a-booking-system-is-not-a-crud-app-58m</link>
      <guid>https://dev.to/oyinda9/series-building-saloonbook-a-booking-system-is-not-a-crud-app-58m</guid>
      <description>&lt;p&gt;The problem, the idea, and the naïve version that worked perfectly ,which was the problem.&lt;/p&gt;

&lt;p&gt;For months I watched the same scene at a salon near me. The owner cutting hair with one hand, thumbing WhatsApp with the other. Three chats open, all variations of "Are you free by 4?" A paper notebook with names half-erased. And every so often, the quiet disaster: two people booked into the same chair, and the awkward shuffle of sending someone to wait at the café next door.&lt;/p&gt;

&lt;p&gt;It's not that these businesses are behind. It's that the cost of the existing system is invisible. The notebook works — right up until it doesn't, and the failure is always silent. A no-show. A double-booking. A regular who drifts away because "I texted and nobody replied." Nobody files a bug report against a paper calendar.&lt;/p&gt;

&lt;p&gt;I'm Nigerian, building for a market where most salons run entirely on WhatsApp Business and bank transfers. There's no Calendly-shaped tool that fits the reality here — the payment rails are different (Paystack, Monnify, raw transfers with proof-of-payment screenshots), the trust model is different, the price sensitivity is brutal. So I started SaloonBook: a multi-tenant salon booking platform in Next.js, TypeScript, Tailwind, and Postgres via Prisma.&lt;/p&gt;

&lt;p&gt;I thought it was a CRUD app. My first booking endpoint did exactly what I told it to — validate input, look up the salon and services, sum price and duration, write a row:&lt;/p&gt;

&lt;p&gt;_&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`const totalPrice = services.reduce((sum, svc) =&amp;gt; sum + svc.price, 0);
const totalDuration = services.reduce((sum, svc) =&amp;gt; sum + svc.duration, 0);

const booking = await prisma.booking.create({
  data: {
    salonId,
    serviceId: serviceIds[0],
    bookingDate: new Date(bookingDate),
    startTime,
    endTime: calculateEndTime(startTime, totalDuration),
    status: BookingStatus.PENDING,
    paymentStatus: PaymentStatus.PENDING,
    totalPrice,
    ...(staffId &amp;amp;&amp;amp; { staffId }),
  },
});
It computed the end time correctly:


function calculateEndTime(startTime: string, duration: number): string {
  const [hours, minutes] = startTime.split(":").map(Number);
  const totalMinutes = hours * 60 + minutes + duration;
  const endHours = Math.floor(totalMinutes / 60) % 24;
  const endMinutes = totalMinutes % 60;
  return `&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endHours&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}:&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endMinutes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`;
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;_&lt;br&gt;
It worked. I booked a 45-minute haircut at 2:00 PM. Then I booked another at 2:15 PM.&lt;/p&gt;

&lt;p&gt;The system cheerfully accepted both.&lt;/p&gt;

&lt;p&gt;And that's where I realized I hadn't built a booking system at all. I'd built a very polite way to record collisions. Next episode: why "do these two bookings overlap?" is the wrong question.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
