┌─ Case study ── 2026 ── ● Shipped
NaijaStay
Hotel booking & operations API
A backend API for managing hotel rooms, guests, staff, bookings, availability, and day-to-day hotel operations. Built around the practical problems of a real hotel: room availability, temporary holds, booking conflicts, staff permissions, check-in/check-out workflows, housekeeping states, caching, rate limiting, and migrations.
── Problem
A hotel booking system has to do more than store a reservation. When a guest searches for a room, the system must determine whether that room is genuinely available for the requested dates — not just whether an is_available flag is set. If a guest temporarily holds a room while completing a booking, another guest must not be able to take the same room during that period. And hotel employees should not all have the same level of access.
── Highlights
- 2,814 LOC across domain modules: rooms, bookings, payments, users, auth
- 102 automated tests across 13 test files
- 4-role RBAC (guest, receptionist, housekeeper, manager) via reusable dependencies
- Pessimistic locking to prevent double-bookings
- Server-side price calculation — client never trusted for totals
- Redis-backed temporary room holds with expiry
- Alembic migrations instead of create_all()
- Docker Compose with healthcheck gating so the API waits for Postgres and Redis
- SSE streaming for room status, APScheduler for background jobs
- Middleware-based request logging and SlowAPI rate limiting
── Decisions
- Chose pessimistic locking for booking creation — the double-booking window is rare but catastrophic, so correctness beats throughput
- Separated booking status from room-cleaning status: a room is not ready just because the previous booking ended
- Cached read-heavy endpoints only, with invalidation on room or pricing changes — never cached booking operations
- Used SQLModel over raw SQLAlchemy for schema-first models with Pydantic validation, accepting the abstraction cost for development speed
- Centralized logging in middleware so routers stay focused on business logic
── Constraints
- PostgreSQL is the source of truth; Redis is a performance layer, never the authority
- Booking price must be calculated server-side — a client-supplied total is a vulnerability
- Four roles with genuinely different permissions, not one role with if-statements
- Schema changes must be explicit and reproducible across environments
- The API must start only when its dependencies are healthy
── Architecture
- Domain-oriented layout: app/domains/{users,rooms,bookings,payments,auth} each own models, schemas, service, and router
- Routers handle HTTP concerns only; business rules live in service layers where they are reusable and independently testable
- Reusable role dependencies enforce authorization instead of duplicating permission checks
- Redis holds are consumed on booking completion or expire naturally
- Alembic migrations manage all schema changes; create_all() is never used in normal operation
- Docker Compose orchestrates API + PostgreSQL + Redis with healthchecks and dependency ordering
── Next steps
- Payment gateway integration (currently a mock provider)
- Email/SMS booking confirmations
- Automated expired-hold cleanup job
- CI/CD pipeline and production observability
- API versioning and a frontend client
── Stack
Next project: Iya Ngozi Kitchen →