Skip to content

daphnechiu/escape-room-api

Repository files navigation

Escape Room API

Minimal Next.js app for the escape room booking take-home.

If you are reviewing the implementation, the core files to focus on are:

  • app/api/holds/route.ts
  • app/api/holds/[holdId]/route.ts
  • app/api/holds/[holdId]/confirm/route.ts
  • lib/booking.ts
  • prisma/schema.prisma
  • test/booking.test.ts

Run locally

Create a .env file in the project root with:

DATABASE_URL="file:./dev.db"

Then run:

npm install
npx prisma generate
npx prisma db push --force-reset
npm run db:seed
npm run dev

Open http://localhost:3000.

db push --force-reset is the quickest local reset here because the schema changed a few times during the take-home and the checked-in dev.db may still reflect an older shape.

Data model

  • Room stores the room catalog.
  • Reservation stores both temporary holds and confirmed bookings for a room at a concrete start time.

The API only accepts startsAt for a reservation request. Slot length is fixed at one hour.

Why this is pragmatic

  • It avoids pre-generating slots forever into the future.
  • It keeps the schema small and easy to explain.
  • It keeps rooms as reference data and reservations as the only dynamic state in the database.
  • For this take-home, SQLite's single-writer behavior plus transactional reservation logic is a pragmatic way to prevent double booking without adding extra infrastructure.

Time-boxing and simplicity

This was built as a short take-home, so I intentionally kept the implementation small:

  • one Reservation table handles hold, confirm, and release
  • the API only exposes the three required actions
  • slot length is fixed to one hour to avoid extra scheduling complexity
  • validation is intentionally minimal so the core hold lifecycle stays easy to follow
  • minimal tests

With more time, I would have:

  • fuller request validation for fields like email and datetime shape
  • allow more dynamic time slots
  • better and more complete tests
  • idempotency or safer retry handling for confirm/release actions
  • stronger database-backed concurrency guarantees if this moved beyond SQLite or needed to support heavier contention

Single-table tradeoff

I intentionally removed a separate hold table and modeled the lifecycle on a single Reservation row with statuses like HELD, CONFIRMED, and RELEASED.

That simplifies the confirm flow from "create a booking from a hold" into "update a reservation status", but it also means uniqueness for an active slot is enforced in application logic rather than with a simple database unique constraint. A plain unique key on roomId + startsAt would incorrectly block future reservations after a hold is released or its holdExpiresAt has passed.

API surface

  • POST /api/holds creates a 5-minute hold for a room and start time
  • POST /api/holds/:holdId/confirm confirms a hold for the same email that created it
  • DELETE /api/holds/:holdId releases a hold for the same email that created it

Manual API testing

Create a hold:

curl -X POST http://localhost:3000/api/holds \
  -H "Content-Type: application/json" \
  -d '{
    "roomId": "1",
    "email": "user@example.com",
    "startsAt": "2026-04-12T10:00:00-07:00"
  }'

Copy the returned hold.id, then confirm it:

curl -X POST http://localhost:3000/api/holds/<holdId>/confirm \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com"
  }'

Or release it instead:

curl -X DELETE http://localhost:3000/api/holds/<holdId> \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com"
  }'

Quick sanity checks:

  • an empty POST /api/holds should return 400
  • creating the same hold twice before expiry should return 409
  • confirming a released or expired hold should return 409
  • confirming or releasing a hold with a different email should return 409

Testing

npm run test

I kept the automated tests intentionally small and focused on the reservation rules that matter most for this exercise.

With more time, I would add API-level integration tests that exercise the real route handlers and database together, including:

  • creating a hold through POST /api/holds
  • confirming a hold through POST /api/holds/:holdId/confirm
  • releasing a hold through DELETE /api/holds/:holdId
  • end-to-end duplicate hold / duplicate booking scenarios against a dedicated test database

AI usage

I used AI during development to speed up iteration on boilerplate, refactors, help with logic, and this README.

What AI helped with:

  • scaffolding and cleanup of the initial project structure
  • iterating on Prisma schema shape and route organization
  • tightening tests and error handling
  • refining README wording and tradeoff explanations

What I made sure of:

  • I kept the implementation intentionally small enough that I can explain every file and every decision in an interview
  • I reviewed and simplified generated suggestions rather than pasting them in blindly
  • I only kept code paths that I understand and can defend, especially around reservation lifecycle and concurrency assumptions

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors