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.
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
- API design: routes, status codes, validation.
- Data model and double-booking prevention.
- Authentication and authorisation.
- Testing strategy.
- 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
subandrole; 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.
/healthzand/readyz(database ping); SIGTERM drains connections; image runs as non-root.- helmet, CORS limited to the app's origin, body size limits,
trust proxyset 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.