Skip to main content
Early access — new tools and guides added regularly
🔵 Build Real Projects — Guide 12 of 16
View track
>_ claude codeIntermediate40 min

Build a Booking System

Build a complete booking system with calendar views, availability management, email confirmations, and conflict prevention — the kind of system real businesses use every day.

What you will build
A working booking system with calendar UI, availability slots, and email confirmations

Planning the booking system architecture

A booking system has three core concepts: resources (what can be booked — rooms, people, equipment), availability (when resources are available), and bookings (confirmed reservations). Every booking system — from Calendly to hotel reservations — is built on these three pillars. Ask Claude Code: Create a new Next.js project for a booking system. Set up the project with TypeScript and Tailwind CSS. Create a types file at src/lib/types.ts with interfaces for Resource (id, name, description, duration options in minutes, buffer time between bookings), Availability (resource id, day of week, start time, end time), and Booking (id, resource id, customer name, customer email, start datetime, end datetime, status as pending or confirmed or cancelled, created at timestamp). Then create a data file at src/lib/data.ts with sample data: a consultation resource with 30 and 60 minute options, a meeting room with 60 and 120 minute options, and availability for both resources Monday through Friday from 9am to 5pm. Run npm run dev and verify the project starts. The data model is deliberately simple — three entities with clear relationships. The resource defines what is being booked. The availability defines when it can be booked. The booking records a specific reservation. Ask Claude Code: Create a utility file at src/lib/booking-utils.ts with a function that takes a resource id and a date, looks up the availability for that day of the week, and returns all available time slots accounting for the resource duration and buffer time. This is the core algorithm of the entire system.

Building the calendar interface

The calendar is what users interact with. Ask Claude Code: Create a calendar component at src/components/BookingCalendar.tsx. It should show a monthly view with a grid of days. The current day should be highlighted. Days in the past should be greyed out and unclickable. Days with available slots should have a coloured dot indicator. Include navigation arrows to move between months. The header should show the month name and year. Use Tailwind for all styling. Make it a client component since it needs click handlers. The calendar should accept an onDateSelect callback prop that fires when a user clicks a valid date. Build the calendar from scratch using date arithmetic rather than a library — this gives you full control over the design. The key calculation is: find the first day of the month, determine which day of the week it falls on, then render 35 or 42 cells (5 or 6 weeks) starting from the correct offset. Ask Claude Code: Create a page at src/app/book/page.tsx that shows the calendar component. When a user clicks a date, show the available time slots for that date below the calendar as clickable buttons. Each button shows the start time in 12-hour format. Clicking a slot should highlight it as selected and show a booking form with fields for name and email. Run npm run dev and test the flow: navigate months, click a date, see available slots, select a slot, and see the form appear. If the slot calculation is wrong — for example showing slots outside business hours or overlapping with buffer time — ask Claude Code: The slots for Tuesday show a 5:30 PM option but availability ends at 5:00 PM. Fix the slot generation to respect the end time minus the booking duration.

Handling bookings and preventing conflicts

The critical challenge in any booking system is preventing double-bookings — two people booking the same slot simultaneously. Ask Claude Code: Create an API route at src/app/api/bookings/route.ts with POST to create a booking and GET to list bookings. The POST handler should validate the request body (resource id, customer name, email, start time, end time), check for conflicts with existing bookings, and return either the new booking or an error if the slot is taken. Use an in-memory array for storage initially. Add a mutex or lock mechanism to prevent race conditions where two requests check availability simultaneously and both proceed. For the conflict check, the logic is: a new booking conflicts with an existing one if they overlap in time for the same resource. Two time ranges overlap if the new start is before the existing end AND the new end is after the existing start. This single condition catches all overlap scenarios: partial overlap, complete overlap, and containment. Ask Claude Code: Create a test file that simulates two simultaneous booking attempts for the same slot. Run both requests in parallel using Promise.all. Only one should succeed, and the other should return a 409 Conflict status. Run the test and verify. Then ask Claude Code: Add a buffer time check. If a resource has a 15-minute buffer, a booking at 10:00-10:30 should block the 10:30 slot because the buffer extends to 10:45. Update the conflict check to account for buffer time. Also add booking status management: new bookings start as pending, and an endpoint at PATCH /api/bookings/[id] can change the status to confirmed or cancelled. Cancelled bookings should free up their slot for new bookings.

Email confirmations and reminders

A booking is not real until the customer gets a confirmation email. Ask Claude Code: Set up email sending using Resend. Create a file at src/lib/email.ts with functions for sending a booking confirmation email, a cancellation email, and a reminder email. Each email should be a well-formatted HTML email with the booking details: resource name, date, time, duration, and a link to cancel. Use a clean, professional template. Install the resend package: npm install resend. You need a Resend API key — sign up at resend.com for a free account that allows 100 emails per day. Add RESEND_API_KEY to your .env.local file. Ask Claude Code: Update the booking API route to send a confirmation email when a booking is created. Also send an email to the resource owner or admin email notifying them of the new booking. Include the customer's name and email in the admin notification. Test by creating a booking and checking both email inboxes. If emails are not arriving, check the Resend dashboard for delivery logs. Common issues: the from address must be from a verified domain in Resend, or you can use the onboarding@resend.dev address for testing. Ask Claude Code: Add a cancellation flow. Create a page at src/app/cancel/[bookingId]/page.tsx that shows the booking details and a cancel button. When clicked, it calls the PATCH endpoint to cancel the booking and sends a cancellation email. Include a unique cancellation token in the confirmation email URL so only the person who booked can cancel. Ask Claude Code: Build a reminder system that sends an email 24 hours before a booking. Create a function that checks all confirmed bookings, finds those starting in the next 24 hours that have not had a reminder sent, and sends reminder emails. This would run as a cron job in production.

Admin dashboard for managing bookings

The business owner needs a way to view and manage all bookings. Ask Claude Code: Create an admin page at src/app/admin/page.tsx that shows all bookings in a table with columns for date, time, resource, customer name, customer email, status, and actions. Add filtering by date range, resource, and status. Add a button to confirm pending bookings and a button to cancel any booking. Include a count of bookings per status at the top as summary cards. Use Tailwind for styling with a professional, clean layout. The table should have alternating row colours and be sortable by clicking column headers. Add a day view that shows a visual timeline of all bookings for a specific date — a vertical bar for each resource with coloured blocks representing bookings. This gives the admin an at-a-glance view of their schedule. Ask Claude Code: Add the ability for the admin to block out time for holidays or maintenance. Create a BlockedTime interface with resource id, start datetime, end datetime, and reason. The availability calculation should account for blocked times — slots that overlap with a blocked time should not appear in the booking calendar. Add a section in the admin dashboard to create and manage blocked times. Ask Claude Code: Add a simple stats section to the admin dashboard. Show total bookings this week, confirmation rate (confirmed vs cancelled), most popular booking times, and average bookings per day. Calculate these from the existing booking data. Display them in card components with large numbers and small labels. These statistics help the business owner understand booking patterns and optimise their availability.

Recurring availability and special schedules

Real businesses have complex schedules: different hours on different days, holiday closures, and special extended hours for events. Ask Claude Code: Extend the availability system to support recurring weekly schedules with per-day overrides. The Availability interface should support a regular weekly schedule (Monday 9-5, Tuesday 9-6, Wednesday closed, etc.) plus specific date overrides that either replace the regular schedule for that date or block the entire date. Create a function getEffectiveAvailability(resourceId, date) that checks for a date-specific override first and falls back to the regular weekly schedule. Test with a resource that has regular Monday-Friday 9-5 hours, extended Thursday hours until 8pm, and a holiday closure on a specific date. Ask Claude Code: Add a schedule editor to the admin dashboard. Show a weekly grid where the admin can set start and end times for each day of the week. Include a toggle to mark days as closed. Below the weekly grid, show a calendar where the admin can click specific dates to add overrides. Each override should let the admin set custom hours or mark the date as fully unavailable. Save changes via an API route. The schedule editor should preview the effects of changes: when the admin modifies availability, show how many slots the change adds or removes. This helps them understand the impact before saving. Ask Claude Code: Add support for multiple availability windows per day. A therapist might be available 9am-12pm and 2pm-5pm with a lunch break. Modify the data model and slot generation to support multiple windows. The slot generation should produce slots within each window independently and never span across a gap between windows.

Integrations and calendar sync

A booking system is most valuable when it connects to existing tools. Ask Claude Code: Add an iCalendar (.ics) file download for each booking. When a customer completes a booking, offer a Download Calendar Event button that generates a .ics file with the correct date, time, duration, location, and description. The .ics format is plain text with a specific structure that all calendar applications understand. Create a utility function that takes a booking and returns an .ics string. Set the response headers to Content-Type: text/calendar and Content-Disposition: attachment; filename=booking.ics. Ask Claude Code: Add a public calendar feed URL for each resource. Create an endpoint at /api/calendar/[resourceId].ics that returns all confirmed bookings for that resource as an iCalendar feed. Users can subscribe to this URL in Google Calendar or Apple Calendar and see bookings update automatically. The feed should include VEVENT entries for each confirmed booking and a VFREEBUSY entry showing available times. Test by adding the feed URL to your personal calendar application. Ask Claude Code: Add webhook support for integrations. Create an endpoint that accepts a webhook URL and event types to subscribe to. When a booking is created, confirmed, or cancelled, send a POST request to all registered webhook URLs with the booking data as JSON. This allows businesses to connect the booking system to their CRM, Slack, or other tools. Create a simple webhook tester that logs incoming webhook payloads so integrators can verify their setup. Add retry logic for failed webhook deliveries — retry 3 times with exponential backoff before giving up.

Testing and deploying the booking system

Before deploying, ensure the system handles edge cases. Ask Claude Code: Create a comprehensive test suite for the booking system. Test the following scenarios: booking a valid slot succeeds, booking an unavailable slot fails, booking in the past fails, double-booking prevention works under concurrent requests, cancellation frees up the slot, buffer time is respected, timezone handling is correct, email validation rejects invalid addresses, and the calendar correctly handles month boundaries and leap years. Use the Node.js built-in test runner or install vitest. Run the tests: npm test. All should pass. Ask Claude Code: Add timezone support. Currently the system assumes one timezone. Add a timezone property to each resource and ensure all times are stored in UTC and displayed in the resource's local timezone. Use the Intl.DateTimeFormat API for display and Date.UTC for storage. Test with a resource in New York and one in London — bookings should display correctly for each. For deployment, ask Claude Code: Prepare this project for deployment to Vercel. Replace the in-memory booking storage with a real database. Set up a PostgreSQL database on Railway, create a bookings table, and update the API routes to use the database. Add all required environment variables to a .env.example file. Run vercel to deploy. Test the live URL by making a booking, verifying the confirmation email, and checking the admin dashboard. You now have a production booking system that handles real-world complexity: availability management, conflict prevention, email notifications, admin controls, calendar integration, and timezone support.

Related Lesson

Building Business Applications

This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.

Go to lesson