Build an online booking system for a small business

Customers pick a free time slot, book it and get an email confirmation; the owner sees and cancels bookings in a simple admin page.

28 घंटे intermediate PythonFlaskSQLite/PostgreSQLResendRender

1 Friday evening: rules before code

Booking apps fail on the rules, not the code. Write them down for one real business (a barber, a tutor, a repair shop):

  • Opening hours per weekday (e.g. Mon–Fri 09:00–17:00, Sat 10:00–14:00).
  • Slot length (30 min) and buffer between bookings (0–10 min).
  • How far ahead customers may book (e.g. 30 days) and the minimum notice (e.g. 2 hours).
  • What the customer must give: name, email, phone, a note.

Done when: a friend can read your rules and tell you whether "Saturday 13:45" can be booked.

2 Saturday morning: data model and slots

python -m venv .venv && source .venv/bin/activate
pip install flask flask-sqlalchemy python-dotenv
# models.py
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

class Booking(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    starts_at = db.Column(db.DateTime, nullable=False, unique=True)   # one booking per slot
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(120), nullable=False)
    note = db.Column(db.String(500))
    cancelled = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

Generate the free slots for a day from the rules, then remove the booked ones:

# slots.py
from datetime import datetime, timedelta, time
HOURS = {0: (9, 17), 1: (9, 17), 2: (9, 17), 3: (9, 17), 4: (9, 17), 5: (10, 14)}   # Mon=0
SLOT = timedelta(minutes=30)

def free_slots(day, booked, now):
    if day.weekday() not in HOURS:
        return []
    open_h, close_h = HOURS[day.weekday()]
    t, end = datetime.combine(day, time(open_h)), datetime.combine(day, time(close_h))
    out = []
    while t + SLOT <= end:
        if t not in booked and t >= now + timedelta(hours=2):
            out.append(t)
        t += SLOT
    return out

Done when: free_slots returns the right list for a weekday, a Saturday, a Sunday and "today after 15:00" in a quick test.

3 Saturday afternoon: the booking page

  • GET /book?day=2026-11-03 shows the free slots as buttons.
  • POST /book re-checks the slot is still free inside the request (two people can click the same slot), then inserts. The unique=True on starts_at is the final guard: catch IntegrityError and show "Sorry, that slot was just taken".
  • Validate the email, cap the note length, add CSRF protection (Flask-WTF) and a rate limit (Flask-Limiter, e.g. 10 bookings/hour per IP).

Done when: two browser windows trying to book the same slot end with exactly one booking.

4 Sunday morning: confirmations and cancelling

Send a confirmation email with Resend and a cancel link that carries a signed token, not the booking id:

from itsdangerous import URLSafeTimedSerializer
s = URLSafeTimedSerializer(app.config["SECRET_KEY"], salt="cancel")
token = s.dumps(booking.id)                       # in the email: /cancel/<token>
booking_id = s.loads(token, max_age=60 * 60 * 24 * 30)   # in the route

Add an owner page (/admin, behind a password from an environment variable) listing upcoming bookings with a Cancel button (POST form).

Done when: the customer gets the email, the cancel link frees the slot, and a tampered token is rejected.

5 Sunday afternoon: deploy

  • Move to PostgreSQL on Render (DATABASE_URL), keep secrets in environment variables.
  • Store times in UTC and show them in the business's time zone (zoneinfo.ZoneInfo("Europe/London")).
  • Add the booking link to the business's Google profile and website.

Done when: a real customer books from their phone and both of you get the email.

प्रोजेक्ट पूरा हुआ? लिंक समीक्षा के लिए भेजें। समीक्षा के लिए भेजें (50 अंक)

इन ट्रैक के साथ और गहराई में जाएँ

और प्लेबुक