Content is user-generated and unverified.

Escape Room Booking System

A booking service for escape rooms running on Cloudflare Pages + Functions with D1 database, Resend for emails, and Turnstile for CAPTCHA.

Features

  • Public: Landing page with rooms, booking form with time slot selection
  • Admin: View/cancel bookings (today/upcoming/past), manage rooms (add/hide/delete)
  • Notifications: Email to admin on new bookings
  • CAPTCHA: Turnstile protection on booking form

Setup

Prerequisites

  • Node.js 18+
  • Cloudflare account
  • Resend account

1. Install Wrangler CLI

bash
npm install -g wrangler
wrangler login

2. Install Dependencies

bash
cd escape-room-booking
npm install

3. Create D1 Database

bash
wrangler d1 create escape-rooms-db

Copy the database_id from the output into wrangler.toml:

toml
[[d1_databases]]
binding = "DB"
database_name = "escape-rooms-db"
database_id = "paste-your-id-here"

4. Setup Resend

  1. Create account at resend.com
  2. Copy your API key from the dashboard
  3. Optionally verify a domain for sending (otherwise use onboarding@resend.dev for testing)

5. Setup Turnstile

  1. Go to Cloudflare Dashboard → Turnstile
  2. Click Add site
  3. Enter site name and domain (use localhost for dev)
  4. Copy the Site Key and Secret Key
  5. Replace YOUR_TURNSTILE_SITE_KEY in public/book.html with your Site Key

Test keys (always passes):

  • Site Key: 1x00000000000000000000AA
  • Secret Key: 1x0000000000000000000000000000000AA

6. Generate Admin Password Hash

bash
echo -n "your-password" | shasum -a 256 | cut -d' ' -f1

7. Configure Environment Variables

Create .dev.vars for local development:

env
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD_HASH=your-sha256-hash
RESEND_API_KEY=re_xxxxxxxxxxxxx
FROM_EMAIL=bookings@yourdomain.com
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA

8. Initialize Database

bash
# Local
npm run db:init:local

# Production (after deploy)
npm run db:init

9. Run Locally

bash
npm run dev

Deployment

Option A: CLI

bash
npm run deploy

Then set secrets:

bash
wrangler secret put ADMIN_EMAIL
wrangler secret put ADMIN_PASSWORD_HASH
wrangler secret put RESEND_API_KEY
wrangler secret put FROM_EMAIL
wrangler secret put TURNSTILE_SECRET_KEY

Option B: Cloudflare Dashboard

  1. Pages → Create project → Connect to Git
  2. Build settings:
    • Build command: (leave empty)
    • Output directory: public
  3. Settings → Environment variables: Add all secrets
  4. Settings → Functions → D1 database bindings: Bind DB to your database

After deploy, initialize the production database:

bash
npm run db:init

Project Structure

escape-room-booking/
├── public/
│   ├── index.html          # Landing page
│   ├── book.html           # Booking form
│   ├── styles.css
│   └── admin/
│       └── index.html      # Admin dashboard
├── functions/
│   └── api/
│       ├── rooms.js
│       ├── bookings.js
│       ├── bookings/
│       │   └── slots.js
│       └── admin/
│           ├── _middleware.js
│           ├── login.js
│           ├── bookings.js
│           ├── bookings/[id]/cancel.js
│           ├── rooms.js
│           └── rooms/[id].js
├── schema.sql
├── wrangler.toml
└── package.json

API Reference

Public

MethodEndpointDescription
GET/api/roomsList visible rooms
GET/api/bookings/slots?room_id=X&date=YYYY-MM-DDGet booked time slots
POST/api/bookingsCreate booking

Admin (requires Authorization: Bearer <token>)

MethodEndpointDescription
POST/api/admin/loginGet auth token
GET/api/admin/bookings?filter=today|upcoming|pastList bookings
POST/api/admin/bookings/:id/cancelCancel booking
GET/api/admin/roomsList all rooms
POST/api/admin/roomsCreate room
PATCH/api/admin/rooms/:idUpdate room visibility
DELETE/api/admin/rooms/:idDelete room (fails if bookings exist)

Configuration

Environment Variables

VariableDescription
ADMIN_EMAILAdmin login email
ADMIN_PASSWORD_HASHSHA-256 hash of admin password
RESEND_API_KEYResend API key
FROM_EMAILSender email for notifications
TURNSTILE_SECRET_KEYCloudflare Turnstile secret

Customization

  • Business info: Edit public/index.html (location, hours, contact)
  • Time slots: Edit generateTimeSlots() in public/book.html
  • Styling: Edit CSS variables in public/styles.css
Content is user-generated and unverified.
    README.md | Claude