Node.js Backend Fundamentals with Express · Capstone

Capstone: A Bookings API for a Small Business

Plan and build a production-ready Express API for appointments: data model, validation, auth, errors, tests and deployment.

Create a free account to save this page, take notes and track your progress. Sign in

Scenario

A hairdresser chain with 4 salons wants an API for its booking app: customers see free slots and book; staff see and manage their day. Expected load: a few thousand bookings a day, spikes on Monday mornings.

Requirements

  • Customers register, log in, list free slots per salon and day, create and cancel bookings.
  • Two customers must never book the same slot.
  • Staff (role staff) see bookings for their salon only.
  • Clear errors for invalid input and conflicts.

Constraints

  • Node.js 24, Express 5, PostgreSQL.
  • Deployed as containers behind a load balancer with 2 to 4 instances.

Deliverables

  1. API design: routes, status codes, validation.
  2. Data model and double-booking prevention.
  3. Authentication and authorisation.
  4. Testing strategy.
  5. Production setup: config, logging, health, shutdown, rate limits.

Hints

  • The database is the right place to prevent double bookings.
  • Several instances share nothing in memory.

Draft each deliverable below before revealing the model answer and rubric.

Further reading: Express production best practices · PostgreSQL unique constraints

Deliverables

Design the API.

Model answer

  • POST /auth/register, POST /auth/login (returns a short-lived JWT plus refresh token cookie).
  • GET /salons/:salonId/slots?date=YYYY-MM-DD (validated date, max 31 days ahead).
  • POST /bookings {salonId, startsAt, service} -> 201 with Location; 409 if the slot is taken; 400 for invalid input.
  • DELETE /bookings/:id -> 204 (owner or staff of that salon), 404 if not found or not visible.
  • GET /staff/bookings?date= for staff.
  • Zod schemas per route; JSON errors with field details; a central error handler.

Rubric

Look for resource-oriented routes, correct status codes (201, 204, 400, 401, 403/404, 409) and schema validation.

How is double booking prevented?

Model answer

  • Table bookings(id, salon_id, starts_at, customer_id, status, ...) with a unique constraint on (salon_id, starts_at) where status is active (a partial unique index).
  • The insert is a single statement; a unique-violation error maps to 409 "slot already booked".
  • No check-then-insert in application code (it races across instances).

Rubric

Strong answers rely on a database constraint and map the violation to 409, rather than in-memory locks or check-then-insert.

Describe authentication and authorisation.

Model answer

  • Passwords hashed with argon2 or scrypt; login rate-limited per IP and per account (shared Redis store).
  • Access JWT (15 minutes) with sub and role; refresh token in an httpOnly cookie, stored hashed server-side so it can be revoked.
  • Middleware requireRole("staff"); staff queries always filter by the staff member's salon from the database, never from client input.

Rubric

Look for slow password hashing, short-lived tokens with revocation, and authorisation enforced server-side per salon.

What is the testing strategy?

Model answer

  • createApp(deps) factory; tests with node:test against a real PostgreSQL in a container (Testcontainers or Compose) with migrations applied.
  • Cases: validation errors, auth failures, double booking (two concurrent requests, exactly one 201), staff isolation between salons, cancellation rules.
  • CI runs tests on every pull request.

Rubric

Strong answers test against a real database, include a concurrency test for double booking, and cover authorisation boundaries.

Describe the production setup.

Model answer

  • Config validated at startup (DATABASE_URL, JWT keys, REDIS_URL); pino JSON logs with request IDs.
  • /healthz and /readyz (database ping); SIGTERM drains connections; image runs as non-root.
  • helmet, CORS limited to the app's origin, body size limits, trust proxy set for the load balancer, rate limits in Redis.
  • Connection pool sized per instance so 4 instances stay within PostgreSQL's connection limit.

Rubric

Look for validated config, structured logs, health checks, graceful shutdown, security middleware with shared rate-limit storage and connection budgeting.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?