Back to Blog
Developer programming at a desk with laptop and external monitor showing code on screen
Tutorial
July 6, 2026
10 min read

How to Build a Booking System in JavaScript (Without the Enterprise Price Tag)

By SimpleCalendarJS Team

SimpleCalendarJS~14 KB gzipped · Zero dependencies · Any framework

Every SaaS dashboard, clinic website, and coworking space eventually needs the same thing: a booking system. Users pick a date, choose a time slot, and reserve it. The concept is simple — but the moment you search for a JavaScript booking solution, you're hit with commercial widgets starting at $395, enterprise schedulers quoting $600+ per developer, and calendar libraries that need six plugins before they render a month grid. You don't need any of that to build a working booking system.

What a booking system actually requires

Before choosing tools, break the problem into its real components. A booking system is not a single widget — it's three distinct pieces working together:

  1. Calendar UI — lets users browse dates and select time slots visually
  2. Booking logic — validates availability, prevents conflicts, stores reservations
  3. Confirmation flow — sends emails, shows success states, handles cancellations

Most commercial "booking widgets" bundle all three into a monolithic package. That sounds convenient until you need to customise the confirmation email, connect to your existing user database, or change the slot duration logic. Then you're fighting the widget instead of building your product.

The better approach: pick a lightweight calendar for the UI and build the booking logic yourself. You control every decision, and the calendar stays swappable.

The commercial booking widget landscape

If you've researched this space, you've seen these names:

SolutionStarting PriceBundle SizeLock-in
DHTMLX Booking$599+ (Scheduling Pack)~100 KB+DHTMLX ecosystem
Mobiscroll$395/license~80 KB+Per-project license
FullCalendar Premium$480/developer~43 KB + pluginsPlugin architecture
Bryntum CalendarCustom pricing~200 KB+Bryntum ecosystem
SimpleCalendarJSFree~14 KBNone

DHTMLX Booking is a dedicated booking widget — it renders provider cards, available time slots, and a confirmation step. But it's an add-on to DHTMLX Scheduler, which means you're buying into their component ecosystem. The Scheduling Pack starts at $206/developer and goes up from there.

Mobiscroll offers a polished, mobile-first booking calendar with per-day pricing labels and slot management. Licenses start at $395 and are locked to a single project. Renewal costs 80% of the license fee annually for updates and support.

FullCalendar is free for basic calendar views, but resource scheduling — the feature you need to show rooms, staff, or equipment side by side — requires Premium plugins at $480+ per developer (GitHub #793). And FullCalendar v6 shipped with a 1000%+ bundle size increase in the @fullcalendar/common package (GitHub #7029), which has made developers cautious about upgrades.

These tools solve real problems. But if your booking system is a clinic with 3 doctors, a barbershop with 5 chairs, or a meeting room scheduler for your team — you don't need enterprise scheduling. You need a calendar and some server-side logic.

Building a booking system with SimpleCalendarJS

Here's the approach: use SimpleCalendarJS for the calendar UI and a simple backend API for booking logic. The calendar handles date browsing and slot selection. The server handles everything else.

Step 1: Set up the calendar

npm install simple-calendar-js
import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/dist/simple-calendar-js.min.css'; const calendar = new SimpleCalendarJs('#booking-calendar', { defaultView: 'week', locale: 'en-US', enabledViews: ['week', 'day'], // Load existing bookings from your API fetchEvents: async (start, end) => { const res = await fetch( `/api/bookings?from=${start.toISOString()}&to=${end.toISOString()}` ); const bookings = await res.json(); return bookings.map((b) => ({ id: b.id, title: b.customerName, start: new Date(b.startTime), end: new Date(b.endTime), color: b.confirmed ? '#10b981' : '#f59e0b', })); }, // Open booking form when user clicks an empty slot onSlotClick: (date) => { openBookingModal({ selectedDate: date }); }, // Show booking details when user clicks an existing booking onEventClick: (event) => { openBookingDetails({ bookingId: event.id }); }, });

This gives you a navigable week/day calendar that shows existing bookings colour-coded by status (green for confirmed, amber for pending) and opens a booking form when users click empty slots. ~14 KB gzipped, zero dependencies.

Step 2: Build the time slot selector

When a user clicks a slot, show available times for that day. Query your backend for open slots:

async function openBookingModal({ selectedDate }) { // Fetch available slots for the selected date const dateStr = selectedDate.toISOString().split('T')[0]; const res = await fetch(`/api/available-slots?date=${dateStr}`); const slots = await res.json(); // Render slot buttons in your modal const slotContainer = document.getElementById('slot-list'); slotContainer.innerHTML = ''; slots.forEach((slot) => { const btn = document.createElement('button'); btn.textContent = `${slot.start}${slot.end}`; btn.className = 'slot-btn'; btn.addEventListener('click', () => selectSlot(slot)); slotContainer.appendChild(btn); }); document.getElementById('booking-modal').showModal(); }

Step 3: Handle the booking submission

async function submitBooking(slot, formData) { const res = await fetch('/api/bookings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: slot.date, startTime: slot.start, endTime: slot.end, customerName: formData.name, customerEmail: formData.email, service: formData.service, }), }); if (res.status === 409) { // Slot was booked by someone else between page load and submission showError('This slot was just booked. Please choose another time.'); return; } const booking = await res.json(); closeModal(); // Refresh the calendar to show the new booking calendar.refetchEvents(); }

The 409 Conflict response is critical. It handles the race condition where two users try to book the same slot simultaneously — the server rejects the second request, and the UI tells the user to pick another time.

Preventing double bookings: the server-side truth

The most dangerous mistake in any booking system is trusting the client. If your "availability check" runs in the browser and your server just accepts whatever the client sends, you will get double bookings under real traffic.

The fix is an atomic database operation. In a single query, check availability and insert the booking:

INSERT INTO bookings (slot_date, start_time, end_time, customer_id) SELECT :date, :start, :end, :customer_id WHERE NOT EXISTS ( SELECT 1 FROM bookings WHERE slot_date = :date AND start_time < :end AND end_time > :start );

If the insert returns zero affected rows, the slot was already booked. Return a 409. This pattern works across every database and prevents race conditions without distributed locks, Redis, or any additional infrastructure.

Feature comparison: booking widget vs. build-your-own

FeatureCommercial WidgetSimpleCalendarJS + Custom Backend
Calendar UIBundled~14 KB, plug and play
Time slot displayBuilt-inCustom (you control the UX)
Double-booking preventionVariesAtomic DB query (rock-solid)
Email confirmationsSome include itYour email service (SendGrid, Resend, etc.)
Custom booking flowLimited by widget APIFully custom
Recurring bookingsPremium tiers onlyYour logic, your rules
Multi-provider viewPaid add-on ($480+)Build what you need
Total cost$395–$600+ per developer$0 (library is free)

The commercial widget wins on time-to-first-demo. If you need a booking page running in 30 minutes and your requirements fit the widget's defaults exactly, pay the license fee. But the moment you need a custom confirmation email, a non-standard slot duration, or integration with your existing auth system, you'll spend more time working around the widget than you saved by using it.

Scaling up: features you'll add later

Once the core booking flow works, extend it incrementally:

Recurring availability

Define provider schedules as recurring rules instead of individual slots:

// Provider works Mon-Fri, 9am-5pm, 30-minute slots const schedule = { providerId: 'dr-smith', days: [1, 2, 3, 4, 5], // Monday through Friday startHour: 9, endHour: 17, slotDuration: 30, // minutes breaks: [{ start: '12:00', end: '13:00' }], };

Buffer time between bookings

Add a gap between consecutive appointments so providers aren't back-to-back:

const BUFFER_MINUTES = 15; function getAvailableSlots(bookings, schedule) { return schedule.slots.filter((slot) => { const slotStart = new Date(slot.start); const slotEnd = new Date(slot.end); return !bookings.some((booking) => { const bookingStart = new Date(booking.startTime); const bookingEnd = new Date( new Date(booking.endTime).getTime() + BUFFER_MINUTES * 60000 ); return slotStart < bookingEnd && slotEnd > bookingStart; }); }); }

Cancellation and rescheduling

Add a cancellation endpoint and update the calendar in real time:

async function cancelBooking(bookingId) { await fetch(`/api/bookings/${bookingId}`, { method: 'DELETE' }); calendar.refetchEvents(); // Calendar updates instantly }

The refetchEvents() method tells SimpleCalendarJS to re-call your fetchEvents function for the current view range. The cancelled booking disappears from the calendar without a page reload.

Summary

  • A booking system is three pieces: calendar UI, booking logic, and confirmation flow — you don't need a monolithic widget for all three
  • Commercial booking widgets (DHTMLX, Mobiscroll, Bryntum) start at $395–$600+ per developer and lock you into their ecosystem
  • SimpleCalendarJS provides the calendar UI at ~14 KB gzipped with onSlotClick and fetchEvents callbacks that wire directly into your booking flow
  • Double-booking prevention must happen server-side — use atomic database queries, not client-side availability checks
  • Build incrementally: start with date selection and slot booking, then add recurring schedules, buffer times, and cancellations as your product requires

Sources & Further Reading

Research & References

Image Credits

All images free to use under the Pexels License.

Frequently Asked Questions

What JavaScript library is best for building a booking system?

It depends on your budget and complexity. DHTMLX Booking ($599+) and Mobiscroll ($395+) are commercial widgets with built-in booking flows. FullCalendar is free for basic calendar views but charges $480+ per developer for resource scheduling. SimpleCalendarJS (~14 KB, free) provides calendar views with event callbacks you can wire to your own booking logic — no license fees.

Can I build a booking system with vanilla JavaScript?

Yes. A booking system is fundamentally a calendar UI plus backend logic for storing reservations and preventing conflicts. You can use any JavaScript calendar library for the date/time selection and handle slot validation, conflict checks, and persistence on the server. No framework is required.

How do I prevent double bookings in JavaScript?

Double-booking prevention must happen server-side, not in the browser. Use atomic database operations — an UPDATE with a WHERE clause that checks availability in a single query — so two concurrent requests can't book the same slot. Client-side checks alone are vulnerable to race conditions.

Do I need FullCalendar Premium for a booking system?

FullCalendar's free core provides month, week, and day views with event rendering. The Premium plugins ($480+ per developer) add resource scheduling, timeline views, and advanced features. If your booking system doesn't need resource columns (e.g., rooms side by side), the free core — or a lighter alternative like SimpleCalendarJS — is sufficient.

How much does it cost to build a booking calendar with JavaScript?

The calendar UI itself can be free. SimpleCalendarJS and FullCalendar's core are both open-source. The real cost is backend development — database design, API endpoints, conflict handling, and email notifications. Commercial booking widgets ($395–$600+) bundle some of this logic but lock you into their UI and licensing model.

What is the easiest way to add a booking calendar to a website?

Install a lightweight calendar library like SimpleCalendarJS (npm install simple-calendar-js), render it in a container element, and use the onSlotClick callback to open a booking form when users click available time slots. Wire the form submission to a backend API that validates and stores the reservation.

📅

Add a calendar to your app today

Free for personal projects. $49/year or $199 lifetime per commercial project.