A complete guide to the StagePass codebase. Read this before touching anything.
StagePass is a movie ticket booking web app — think BookMyShow. Users can browse movies, pick a showtime, select seats, and make a booking. It is intentionally built with a number of bugs, security issues, and code quality problems as a workshop demo for AI-assisted SDLC improvement.
| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Tailwind CSS, Vite |
| Backend | Node.js, Express, TypeScript |
| Database | SQLite (via better-sqlite3) |
| Auth | JWT (jsonwebtoken) |
| Monorepo | pnpm workspaces |
| Shared types | @stagepass/common package |
stagepass/
├── package.json # Root — workspace scripts (dev, build, seed)
├── pnpm-workspace.yaml # Declares packages/* as workspaces
├── .gitignore
│
├── packages/
│ ├── common/ # Shared TypeScript types used by both server and web
│ │ └── src/index.ts
│ │
│ ├── server/ # Express REST API
│ │ ├── package.json
│ │ ├── tsconfig.json
│ │ ├── stagepass.db # SQLite database file (generated by seed)
│ │ └── src/
│ │ ├── index.ts # App entry — mounts routes, starts server on :3001
│ │ ├── db.ts # DB connection + schema creation
│ │ ├── seed.ts # Seeds 25 movies, 880+ showtimes, seats, users
│ │ └── routes/
│ │ ├── auth.ts # POST /api/auth/login, /signup + JWT middleware
│ │ ├── movies.ts # GET /api/movies, /movies/:id, /movies/meta/genres
│ │ ├── showtimes.ts # GET /api/showtimes/movie/:id, /showtimes/:id
│ │ ├── seats.ts # GET /api/seats/showtime/:id
│ │ └── bookings.ts # GET /api/bookings, POST /api/bookings
│ │
│ └── web/ # React frontend
│ ├── package.json
│ ├── tsconfig.json
│ ├── vite.config.ts # Proxies /api → localhost:3001
│ ├── tailwind.config.js
│ ├── index.html
│ └── src/
│ ├── main.tsx # React root
│ ├── App.tsx # Router, Header, AuthProvider wiring
│ ├── AuthContext.tsx # Auth state: user, token, login, signup, logout
│ ├── index.css # Global styles (intentionally ugly)
│ └── pages/
│ ├── Home.tsx # Movie grid + search + genre filter
│ ├── MovieDetail.tsx # Movie info + showtime picker
│ ├── SeatSelection.tsx # Interactive seat grid
│ ├── BookingConfirmation.tsx # Post-booking screen
│ ├── MyBookings.tsx # User's booking history
│ └── Login.tsx # Login / signup form
│
└── docs/
├── CODE_BASE_GUIDE.md # This file
├── ISSUES_IN_THE_CODEBASE.md # Catalogue of 33 known issues
├── PLAN_OF_ACTION.md # High-level 5-phase improvement plan
└── INCREMENTAL_CHANGE_PLAN.md # Step-by-step commit plan with test specs
- Node.js ≥ 18
- pnpm (
npm install -g pnpm)
pnpm installpnpm seed
# Creates packages/server/stagepass.db with:
# - 25 movies (real posters via TMDB)
# - 880+ showtimes across 10 venues over 8 days
# - 75,000+ seats
# - 3 users
# - 6 pre-existing bookingspnpm dev
# Web: http://localhost:3000
# API: http://localhost:3001pnpm dev:server # API only
pnpm dev:web # Frontend onlyEmail: john@example.com
Password: password123
The SQLite database is created automatically on first run by packages/server/src/db.ts.
┌─────────┐ ┌───────────┐ ┌───────────┐
│ users │ │ movies │ │ showtimes │
│─────────│ │───────────│ │───────────│
│ id │ │ id │──────▶│ movie_id │
│ name │ │ title │ │ id │
│ email │ │ genre │ │ date │
│ password│ │ duration │ │ time │
└─────────┘ │ rating │ │ venue │
│ │ poster_url│ │ price │
│ │ synopsis │ │ total_seats│
│ │ director │ └───────────┘
│ │ cast_mbrs │ │
│ │ release_dt│ ┌──▼────┐
│ │ language │ │ seats │
│ └───────────┘ │───────│
│ │ id │
│ ┌──────────┐ │ show..│
└────────▶│ bookings │ │ row │
│──────────│ │ number│
│ id │ │is_book│
│ user_id │ └───────┘
│ showtime_│
│ seats │ ← comma-separated seat IDs e.g. "A1,A2,A3"
│ total_amt│
│ book_date│
└──────────┘
seatsinbookingsis stored as a comma-separated string ("A1,A2,A3") — not a join table. This makes querying which seats belong to a booking cumbersome.passwordis stored as plain text — no hashing. (Security issue 3.1)ratingandsynopsisandcast_membersare nullable — the app doesn't handle nulls. (Crash bug 1.1)
Base URL: http://localhost:3001
All protected routes require: Authorization: Bearer <token>
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/login |
No | Login with email + password. Returns { token, user } |
| POST | /api/auth/signup |
No | Create account. Returns { token, user } |
Login request body:
{ "email": "john@example.com", "password": "password123" }Token payload:
{ "userId": 1, "iat": 1234567890, "exp": 1234654290 }| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/movies |
No | All movies. Supports ?genre=Action and ?search=inception |
| GET | /api/movies/:id |
No | Single movie by ID |
| GET | /api/movies/meta/genres |
No | List of distinct genres |
Movie object:
{
"id": 1,
"title": "Inception",
"genre": "Sci-Fi",
"duration": 148,
"rating": 8.8,
"poster_url": "https://image.tmdb.org/t/p/w500/...",
"synopsis": "A thief who...",
"director": "Christopher Nolan",
"cast_members": "Leonardo DiCaprio, Joseph Gordon-Levitt",
"release_date": "2010-07-16",
"language": "English"
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/showtimes/movie/:movieId |
No | All showtimes for a movie, ordered by date + time |
| GET | /api/showtimes/:id |
No | Single showtime by ID |
Showtime object:
{
"id": 42,
"movie_id": 1,
"date": "2026-02-20",
"time": "7:00 PM",
"venue": "PVR IMAX - Forum Mall",
"price": 350,
"total_seats": 120
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/seats/showtime/:showtimeId |
No | All seats for a showtime |
Seat object:
{
"id": 1201,
"showtime_id": 42,
"row": "D",
"number": 5,
"is_booked": 0
}is_booked is 0 (available) or 1 (booked). SQLite stores booleans as integers.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/bookings |
Yes | All bookings for the logged-in user |
| POST | /api/bookings |
Yes | Create a new booking |
POST request body:
{
"showtimeId": 42,
"seatIds": [1201, 1202, 1203],
"totalAmount": 1050
}GET response (with joined movie + showtime data):
{
"id": 7,
"showtime_id": 42,
"seats": "1201,1202,1203",
"total_amount": 1050,
"booking_date": "2026-02-18T11:45:00",
"movie_title": "Inception",
"venue": "PVR IMAX - Forum Mall",
"show_date": "2026-02-20",
"show_time": "7:00 PM"
}Defined in App.tsx. All routes are public except My Bookings, which redirects to /login if the user is not authenticated (handled inside the component, not via a route guard).
/ → Home.tsx
/movie/:id → MovieDetail.tsx
/seats/:showtimeId → SeatSelection.tsx
/booking-confirmation/:id → BookingConfirmation.tsx
/bookings → MyBookings.tsx
/login → Login.tsx
Managed by AuthContext.tsx. It exposes { user, token, login, signup, logout } to the entire app via React Context.
User submits login form
→ Login.tsx calls login(email, password)
→ AuthContext POSTs to /api/auth/login
→ On success: sets user + token in React state
→ token is NOT persisted to localStorage (bug — page refresh logs you out)
Protected API calls pass the token in the Authorization header:
headers: { 'Authorization': `Bearer ${token}` }There is no data-fetching library (no React Query, no SWR). Every page calls fetch() directly in a useEffect. There is no caching, no retry logic, and no error handling.
Full details in docs/ISSUES_IN_THE_CODEBASE.md.
| # | File | Bug | Trigger |
|---|---|---|---|
| 1 | MovieDetail.tsx:L44 |
cast_members.split() on null → white screen crash |
Click "Untitled Project X" |
| 2 | SeatSelection.tsx:L52-56 |
selectedSeats.splice() mutates state directly → seats don't deselect visually |
Select a seat, then click it again |
| 3 | MyBookings.tsx:L29-33 |
setLoading(false) only called if data.length > 0 → infinite spinner |
Log in as a new user with no bookings |
| 4 | Home.tsx:L44-49 |
while(Date.now() - start < 2000) blocks main thread → UI freezes 2s |
Type anything in the search box |
The seed script (packages/server/src/seed.ts) populates the database with realistic data.
Movies (25 total):
Real films with TMDB poster images. Mix of English, Hindi, Telugu, Korean, Japanese language films. One movie ("Untitled Project X") intentionally has null for rating, synopsis, and cast_members to trigger crash bugs.
Showtimes: Each movie gets 3–6 shows per day across 10 venues over 8 days. Venues include INOX, PVR IMAX, Cinepolis, Regal, and Miraj. Prices range from ₹150 to ₹650. IMAX screens have 120 seats; standard screens have 80.
Seats: Every showtime has a full seat grid: rows A–H (standard) or A–L (IMAX), 10 seats per row. ~15–35% of seats are randomly pre-booked.
Users:
john@example.com / password123 (has 4 pre-existing bookings)
jane@example.com / password123 (has 2 pre-existing bookings)
raj@example.com / password123 (has no bookings — triggers infinite spinner bug)
Warning: Running
pnpm seeddeletes all existing data first. Do not run against a database with real bookings.
- Create
packages/server/src/routes/myroute.ts - Define a Router and export it as default
- Mount it in
packages/server/src/index.ts:import myRouter from './routes/myroute'; app.use('/api/myroute', myRouter);
- Create
packages/web/src/pages/MyPage.tsx - Add a route in
App.tsx:<Route path="/my-page" element={<MyPage />} />
- Add the interface to
packages/common/src/index.ts - Import it in either package:
import type { MyType } from '@stagepass/common';
rm packages/server/stagepass.db
pnpm seed| Command | What it does |
|---|---|
pnpm install |
Install all workspace dependencies |
pnpm dev |
Start server (:3001) + web (:3000) in parallel |
pnpm dev:server |
Start API server only |
pnpm dev:web |
Start frontend only |
pnpm build |
Build all packages |
pnpm seed |
Wipe DB and reseed with fresh data |