Back to Blog
·7 min

Appointment Booking App Template: Build a Scheduling Platform

Appointment booking systems power healthcare, beauty, fitness, consulting, and service businesses. The market for scheduling software continues to grow as more service businesses move online. Building a booking platform requires calendar management, availability logic, payment processing, and notification systems.

An appointment booking app template provides all of this infrastructure pre-built.

Key Platform Features

A production-ready booking template includes:

  • Service provider profiles
  • Availability calendar with time slots
  • Online booking with instant confirmation
  • Payment collection at booking
  • Automated reminders (email + SMS)
  • Cancellation and rescheduling
  • Staff management
  • Customer history

The RentEasy template includes booking and scheduling functionality.

Availability Calendar

// lib/booking/availability.ts
import { prisma } from '@/lib/prisma'

interface TimeSlot {
  start: Date
  end: Date
  available: boolean
}

export async function getAvailableSlots(
  providerId: string,
  date: Date,
  serviceDuration: number
): Promise<TimeSlot[]> {
  const dayOfWeek = date.getDay()
  const availability = await prisma.availability.findFirst({
    where: { providerId, dayOfWeek },
  })

  if (!availability) return []

  const existingBookings = await prisma.booking.findMany({
    where: {
      providerId,
      date: {
        gte: new Date(date.setHours(0, 0, 0, 0)),
        lt: new Date(date.setHours(23, 59, 59, 999)),
      },
      status: { not: 'cancelled' },
    },
  })

  const slots: TimeSlot[] = []
  const startHour = availability.startHour
  const endHour = availability.endHour

  for (let hour = startHour; hour < endHour; hour++) {
    const slotStart = new Date(date)
    slotStart.setHours(hour, 0, 0, 0)
    const slotEnd = new Date(slotStart)
    slotEnd.setMinutes(slotStart.getMinutes() + serviceDuration)

    const conflict = existingBookings.some(booking => {
      const bStart = new Date(booking.date)
      const bEnd = new Date(bStart)
      bEnd.setMinutes(bStart.getMinutes() + booking.duration)
      return slotStart < bEnd && slotEnd > bStart
    })

    slots.push({
      start: slotStart,
      end: slotEnd,
      available: !conflict,
    })
  }

  return slots
}

Booking Widget

'use client'

import { useState } from 'react'
import { Calendar } from 'lucide-react'

interface TimeSlot {
  time: string
  available: boolean
}

export function BookingWidget() {
  const [selectedDate, setSelectedDate] = useState('')
  const [selectedSlot, setSelectedSlot] = useState('')
  const [slots, setSlots] = useState<TimeSlot[]>([])

  const handleDateChange = async (date: string) => {
    setSelectedDate(date)
    setSelectedSlot('')
    const res = await fetch(`/api/slots?date=${date}`)
    const data = await res.json()
    setSlots(data)
  }

  return (
    <div className="rounded-xl border bg-white p-6">
      <h3 className="text-lg font-semibold">Book an Appointment</h3>
      <div className="mt-4">
        <label className="text-sm font-medium">Select Date</label>
        <div className="relative mt-1">
          <Calendar className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
          <input
            type="date"
            value={selectedDate}
            onChange={e => handleDateChange(e.target.value)}
            className="w-full rounded-xl border py-2.5 pl-10 pr-4"
          />
        </div>
      </div>
      {slots.length > 0 && (
        <div className="mt-4">
          <label className="text-sm font-medium">Available Times</label>
          <div className="mt-2 grid grid-cols-3 gap-2">
            {slots.map((slot, i) => (
              <button
                key={i}
                onClick={() => setSelectedSlot(slot.time)}
                disabled={!slot.available}
                className={`rounded-lg border py-2 text-sm font-medium ${
                  selectedSlot === slot.time
                    ? 'border-brand-600 bg-brand-50 text-brand-700'
                    : slot.available
                      ? 'hover:border-brand-300'
                      : 'bg-gray-50 text-gray-300 cursor-not-allowed'
                }`}
              >
                {slot.time}
              </button>
            ))}
          </div>
        </div>
      )}
      <button
        disabled={!selectedSlot}
        className="mt-6 w-full rounded-xl bg-brand-600 py-3 font-semibold text-white disabled:opacity-50"
      >
        Confirm Booking
      </button>
    </div>
  )
}

Build your scheduling platform with the RentEasy booking template.

Browse all 200+ templates and starter kits.

Ready to Build?

Get started with our production-ready starter kits and ship your project faster.

Browse Starter Kits