Examples & Use Cases
The Booking Kit is a general-purpose scheduling primitive. The same core APIs power very different booking experiences depending on how you configure them. The examples below show eight real use cases with the key API calls for each: five provider-based (appointments with people) and three resource-based (reservations for spaces).
1. Barber Shop — Multi-Location with Walk-Ins
Section titled “1. Barber Shop — Multi-Location with Walk-Ins”A barber shop with multiple locations and individual barber schedules. Each barber has their own weekly availability stored as simple JSON in Cloudflare D1. The @thebookingkit/d1 adapter converts these into the RRULE-based rules that @thebookingkit/core expects.
Stack: TanStack Start (or Next.js), Cloudflare D1 (or Postgres), @thebookingkit/core, @thebookingkit/d1, Drizzle ORM.
import { toWeeklyAvailabilityRules } from '@thebookingkit/d1';import { getAvailableSlots } from '@thebookingkit/core';
// Each barber stores their schedule as a simple JSON object in D1.// The adapter converts this to RRULE-based AvailabilityRuleInput[] that// @thebookingkit/core understands.const barberSchedule = { monday: { startTime: '09:00', endTime: '18:00', isOff: false }, tuesday: { startTime: '09:00', endTime: '18:00', isOff: false }, wednesday: { startTime: '09:00', endTime: '18:00', isOff: false }, thursday: { startTime: '09:00', endTime: '21:00', isOff: false }, friday: { startTime: '09:00', endTime: '18:00', isOff: false }, saturday: { startTime: '08:00', endTime: '16:00', isOff: false }, sunday: { startTime: null, endTime: null, isOff: true },};
const availabilityRules = toWeeklyAvailabilityRules( barberSchedule, 'Australia/Sydney',);
// Compute available 30-minute slots for a given barber on a given day.const slots = getAvailableSlots({ date: new Date('2025-06-15'), durationMinutes: 30, // Classic Haircut availabilityRules, existingBookings: barberBookings, // fetched from D1 bufferMinutes: 5, timezone: 'Australia/Sydney',});The kiosk view uses the walk-in queue API to accept walk-in customers alongside scheduled appointments:
import { addWalkIn, estimateWaitTime } from '@thebookingkit/core';
// Receptionist adds a walk-in at the desk.const entry = addWalkIn({ queue: currentQueue, barber: selectedBarber, service: { durationMinutes: 30 }, existingBookings: todaysBookings, walkedInAt: new Date(),});
// Display estimated wait time on the public queue screen.// See the [Walk-In Queue](/features/walk-in) guide for detailsconst { estimatedWaitMinutes } = estimateWaitTime({ queue: currentQueue, position: entry.queuePosition, averageServiceMinutes: 32,});2. Medical Clinic — Round-Robin Doctor Assignment
Section titled “2. Medical Clinic — Round-Robin Doctor Assignment”A medical clinic needs to route appointment requests to available doctors using round-robin assignment, and requires manual approval before confirming any appointment. Patients book a type of appointment (GP visit, specialist review) and the system assigns a doctor — the patient does not pick one directly.
import { getTeamSlots, assignHost } from '@thebookingkit/core';
// Get available slots across the entire GP team.// The team scheduling engine merges individual availability windows// and eliminates conflicts.const teamSlots = getTeamSlots({ date: new Date('2025-06-15'), durationMinutes: 20, // Standard GP visit providers: doctors.map(doctor => ({ id: doctor.id, availabilityRules: doctor.availabilityRules, existingBookings: doctor.bookings, bufferMinutes: 10, // 10-minute buffer between patients })), timezone: 'Australia/Sydney', algorithm: 'round-robin',});
// When a patient selects a slot, assign the next doctor in rotation.const assignedDoctor = assignHost({ slot: selectedSlot, providers: doctors, algorithm: 'round-robin', lastAssignedProviderId: clinic.lastAssignedDoctorId,});
// Create the booking with manual confirmation mode.// The booking is pending until a receptionist approves it.const booking = await createBooking({ slotStart: selectedSlot.start, slotEnd: selectedSlot.end, providerId: assignedDoctor.id, eventType: { confirmationMode: 'manual', // requires staff to confirm autoRejectAfterMinutes: 1440, // auto-reject if not actioned in 24h }, customer: patientDetails,});3. Fitness Studio — Seat-Based Group Classes
Section titled “3. Fitness Studio — Seat-Based Group Classes”A fitness studio runs group classes with a fixed capacity. Multiple participants can book the same time slot up to the seat limit. Classes repeat on the same days each week via a recurring schedule.
import { getAvailableSlots, isSlotAvailable } from '@thebookingkit/core';import { rrulestr } from 'rrule';
// The instructor's recurring availability is defined as an RRULE.// This RRULE expands to every Monday and Wednesday at 07:00.const morningClassRule = { rrule: 'FREQ=WEEKLY;BYDAY=MO,WE;DTSTART=20250101T070000Z', startTime: '07:00', endTime: '08:00', timezone: 'Australia/Melbourne',};
// getAvailableSlots respects the seatCount — a slot is still "available"// if fewer than seatCount bookings exist for it.const slots = getAvailableSlots({ date: new Date('2025-06-16'), // Monday durationMinutes: 60, availabilityRules: [morningClassRule], existingBookings: classBookings, timezone: 'Australia/Melbourne', seatCount: 15, // class capacity});
// Before confirming a new participant, verify the seat is still open.// This is the final check before the database write.const available = isSlotAvailable({ slotStart: selectedSlot.start, slotEnd: selectedSlot.end, existingBookings: classBookings, seatCount: 15,});
if (!available) { throw new Error('This class is now full. Please join the waitlist.');}4. Tutoring Platform — Multi-Timezone Routing Forms
Section titled “4. Tutoring Platform — Multi-Timezone Routing Forms”A tutoring marketplace connects students with tutors across multiple countries. A routing form first asks the student what subject they need, then routes them to tutors who teach that subject. Slots are displayed in the student’s local timezone regardless of where the tutor is located.
import { evaluateRoutingForm, getAvailableSlots } from '@thebookingkit/core';
// The routing form asks for subject and level, then resolves// to a filtered list of tutors and their event types.const routingResult = evaluateRoutingForm({ form: subjectRoutingForm, responses: { subject: 'Mathematics', level: 'Year 12', preferredLanguage: 'English', },});
// routingResult.eventTypeId is the matched tutor's event type.// routingResult.providerId may be null if multiple tutors match// (the student picks from available slots across all matched tutors).
// Fetch available slots for all matching tutors and convert to// the student's local timezone for display.const allSlots = await Promise.all( routingResult.matchedProviders.map(tutor => getAvailableSlots({ date: requestedDate, durationMinutes: 60, availabilityRules: tutor.availabilityRules, existingBookings: tutor.bookings, bufferMinutes: 15, timezone: tutor.timezone, // tutor's own timezone }).map(slot => ({ ...slot, // Convert to student's local timezone for display. displayStart: toZonedTime(slot.start, student.timezone), displayEnd: toZonedTime(slot.end, student.timezone), tutorId: tutor.id, tutorName: tutor.name, })) ));
// Flatten and sort by start time for the student's slot picker.const sortedSlots = allSlots.flat().sort( (a, b) => a.start.getTime() - b.start.getTime());5. Salon with Walk-Ins — Hybrid Appointment + Queue
Section titled “5. Salon with Walk-Ins — Hybrid Appointment + Queue”A hair salon accepts both pre-booked appointments and walk-in customers. Walk-ins are added to a queue; the system finds the next available gap in the stylist’s schedule and slots them in. The public queue display updates in real time so walk-ins can see their estimated wait without asking staff.
import { addWalkIn, estimateWaitTime, findNextAvailableGap, getQueueMetrics,} from '@thebookingkit/core';
// Find the next gap in the stylist's schedule that fits a 45-minute service.// See the [Walk-In Queue](/features/walk-in) guide for detailsconst nextGap = findNextAvailableGap({ existingBookings: stylist.todaysBookings, walkinQueue: stylist.currentQueue, durationMinutes: 45, // Colour + Cut service workingHoursEnd: new Date('2025-06-15T18:00:00+10:00'), now: new Date(),});
// Add the walk-in to the queue at that gap.const walkinEntry = addWalkIn({ queue: stylist.currentQueue, barber: stylist, service: { durationMinutes: 45 }, existingBookings: stylist.todaysBookings, walkedInAt: new Date(), preferredProviderId: stylist.id, // customer asked for a specific stylist});
// Compute wait time for the public-facing display.const waitInfo = estimateWaitTime({ queue: stylist.currentQueue, position: walkinEntry.queuePosition, averageServiceMinutes: 48, // actual average from recent completions});
// Aggregate queue metrics for the salon's front desk summary.// See the [Walk-In Queue](/features/walk-in) guide for detailsconst metrics = getQueueMetrics({ queue: stylist.currentQueue, completedToday: stylist.completedWalkins,});// metrics.averageWaitMinutes, metrics.currentQueueLength, metrics.noShowRate6. Restaurant Reservations — Table Pool with Party Size
Section titled “6. Restaurant Reservations — Table Pool with Party Size”A restaurant manages multiple dining tables with varying capacities. Customers book tables based on party size, and the system auto-assigns the best-fit table (smallest table that fits their party).
import { getResourceAvailableSlots, assignResource } from '@thebookingkit/core';
// Define the restaurant's table poolconst tables = [ { id: 'table-1', name: 'Table 1', type: 'standard', capacity: 2, isActive: true, rules: [{ rrule: 'FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR,SA,SU', startTime: '11:00', endTime: '22:00', timezone: 'America/Los_Angeles', }], overrides: [], bookings: existingReservations, }, { id: 'table-2', name: 'Table 2', type: 'standard', capacity: 4, isActive: true, rules: [{ rrule: 'FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR,SA,SU', startTime: '11:00', endTime: '22:00', timezone: 'America/Los_Angeles', }], overrides: [], bookings: existingReservations, }, { id: 'table-6', name: 'Table 6 (Patio)', type: 'patio', capacity: 6, isActive: true, rules: [{ rrule: 'FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR,SA,SU', startTime: '11:00', endTime: '22:00', timezone: 'America/Los_Angeles', }], overrides: [], bookings: existingReservations, },];
// Find all available time slots across the table poolconst availableSlots = getResourceAvailableSlots( tables, { start: new Date('2026-03-15'), end: new Date('2026-03-16'), }, 'America/Los_Angeles', { duration: 90, // 90-minute seating minCapacity: 4, // Party of 4 bufferAfter: 15, // 15-min turnaround between seatings });
// Automatically assign the best-fit table for a 4-person partyconst assignment = assignResource( tables, new Date('2026-03-15T18:00:00.000Z'), new Date('2026-03-15T19:30:00.000Z'), { strategy: 'best_fit', // Pick the smallest table that fits minCapacity: 4, requestedCapacity: 4, });
console.log(`Assigned ${assignment.resourceName} (${assignment.reason})`);// Output: Assigned Table 2 (best_fit)7. Hotel Room Booking — Multi-Type Resource Pool
Section titled “7. Hotel Room Booking — Multi-Type Resource Pool”A hotel manages multiple room types (standard, deluxe, suite) with varying capacities. A booking spans check-in to check-out, and the system ensures each room is assigned to only one guest at a time.
import { getResourceAvailableSlots, assignResource } from '@thebookingkit/core';
const hotelRooms = [ { id: 'room-101', name: 'Standard Room 101', type: 'standard', capacity: 2, isActive: true, rules: [{ rrule: 'FREQ=DAILY', startTime: '15:00', // 3 PM check-in endTime: '11:00', // 11 AM check-out (next day) timezone: 'America/New_York', }], overrides: [], bookings: existingCheckIns, }, { id: 'room-305', name: 'Deluxe Room 305', type: 'deluxe', capacity: 3, isActive: true, rules: [{ rrule: 'FREQ=DAILY', startTime: '15:00', endTime: '11:00', timezone: 'America/New_York', }], overrides: [], bookings: existingCheckIns, }, { id: 'room-510', name: 'Penthouse Suite', type: 'suite', capacity: 4, isActive: true, rules: [{ rrule: 'FREQ=DAILY', startTime: '15:00', endTime: '11:00', timezone: 'America/New_York', }], overrides: [], bookings: existingCheckIns, },];
// Guest checking in for 2 nights, requesting at least 2-bed capacityconst availableRooms = getResourceAvailableSlots( hotelRooms, { start: new Date('2026-04-01'), // Check-in date end: new Date('2026-04-03'), // Check-out date }, 'America/New_York', { duration: 2880, // 48 hours (2 nights) minCapacity: 2, // Party of 2 resourceType: 'deluxe', // Filter to deluxe rooms only });
// Auto-assign a room for the reservationconst roomAssignment = assignResource( hotelRooms, new Date('2026-04-01T15:00:00.000Z'), new Date('2026-04-03T11:00:00.000Z'), { strategy: 'largest_first', // Prefer larger rooms when available resourceType: 'deluxe', requestedCapacity: 2, });8. Coworking Space — Multi-Resource Types
Section titled “8. Coworking Space — Multi-Resource Types”A coworking space manages desks, meeting rooms, and phone booths. Members book by resource type; the system handles per-resource capacity and availability.
import { getResourceAvailableSlots, assignResource, getResourcePoolSummary } from '@thebookingkit/core';
const coworkingResources = [ { id: 'desk-1', name: 'Desk 1 (Window)', type: 'hot_desk', capacity: 1, isActive: true, rules: [{ rrule: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', startTime: '08:00', endTime: '18:00', timezone: 'Europe/London', }], overrides: [], bookings: deskBookings, }, { id: 'desk-2', name: 'Desk 2', type: 'hot_desk', capacity: 1, isActive: true, rules: [{ rrule: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', startTime: '08:00', endTime: '18:00', timezone: 'Europe/London', }], overrides: [], bookings: deskBookings, }, { id: 'meeting-boardroom', name: 'Boardroom', type: 'meeting_room', capacity: 12, isActive: true, rules: [{ rrule: 'FREQ=DAILY', startTime: '08:00', endTime: '18:00', timezone: 'Europe/London', }], overrides: [], bookings: meetingBookings, }, { id: 'phone-booth-1', name: 'Phone Booth A', type: 'phone_booth', capacity: 1, isActive: true, rules: [{ rrule: 'FREQ=DAILY', startTime: '08:00', endTime: '18:00', timezone: 'Europe/London', }], overrides: [], bookings: phoneBoothBookings, },];
// Member wants to book a meeting room for a 4-person syncconst availableRooms = getResourceAvailableSlots( coworkingResources, { start: new Date('2026-03-17'), end: new Date('2026-03-17'), }, 'Europe/London', { duration: 60, resourceType: 'meeting_room', minCapacity: 4, });
// Admin dashboard: show current utilization across all hot desksconst deskUtilization = getResourcePoolSummary( coworkingResources, { start: new Date('2026-03-17'), end: new Date('2026-03-17'), }, 'Europe/London', { duration: 480, // 8-hour work day resourceType: 'hot_desk', now: new Date(), });
console.log(`Desks available right now: ${deskUtilization[0]?.availableResources}/${deskUtilization[0]?.totalResources}`);What to Build Next
Section titled “What to Build Next”All eight examples above use the same underlying APIs from @thebookingkit/core. Provider booking uses getAvailableSlots and getTeamSlots; resource booking uses getResourceAvailableSlots, assignResource, and getResourcePoolSummary. The difference is configuration: which scheduling algorithm, what capacity model, which timezone, whether confirmation is automatic or manual.
To start integrating The Booking Kit into your own application, follow the Quick Start guide or review the Architecture page for a full picture of how the packages fit together.