A full-stack movie ticket booking application inspired by BookMyShow, featuring real-time seat selection, automated booking workflows, secure payments, and an admin dashboard for show management.
- Frontend: https://book-my-show-green-seven.vercel.app/
- Backend API: https://bookmyshow-server-fawn.vercel.app/
Homepage with trending movies and trailers
Detailed movie information with cast, genres, and ratings
Browse all currently showing movies
Interactive seat selection with real-time availability
User booking history with payment status
Secure payment processing with Stripe
Admin dashboard with revenue analytics and statistics
Add new shows with date-time selection
graph TB
subgraph "Client Layer"
A[React Frontend<br/>Vite + TypeScript]
B[TailwindCSS]
C[Clerk Auth]
end
subgraph "API Gateway"
D[Express.js Server<br/>TypeScript]
E[Clerk Middleware]
F[CORS Handler]
end
subgraph "Business Logic"
G[User Controller]
H[Booking Controller]
I[Show Controller]
J[Admin Controller]
end
subgraph "External Services"
K[Stripe Payment<br/>Gateway]
L[TMDB API<br/>Movie Data]
M[Brevo SMTP<br/>Email Service]
N[Upstash Redis<br/>Seat Locking]
end
subgraph "Background Jobs"
O[Inngest<br/>Workflow Engine]
P[Payment Verification<br/>10 min delay]
Q[Email Notification<br/>On booking success]
end
subgraph "Data Layer"
R[(MongoDB<br/>Primary Database)]
end
A --> D
C --> E
E --> D
F --> D
D --> G
D --> H
D --> I
D --> J
H --> K
I --> L
G --> R
H --> R
I --> R
J --> R
H --> N
K --> O
O --> P
O --> Q
Q --> M
P --> R
flowchart TD
Start([User Opens App]) --> Auth{Authenticated?}
Auth -->|No| Login[Clerk Sign In]
Auth -->|Yes| Home[Browse Movies]
Login --> Home
Home --> MovieDetails[View Movie Details<br/>& Show Times]
MovieDetails --> SelectDate[Select Date & Time]
SelectDate --> SeatSelection[Interactive Seat Layout]
SeatSelection --> CheckAvail{Check Seat<br/>Availability}
CheckAvail -->|Available| LockSeats[Lock Seats in Redis<br/>+ Create Booking]
CheckAvail -->|Occupied| SeatSelection
LockSeats --> CreateStripe[Create Stripe<br/>Checkout Session]
CreateStripe --> TriggerWorkflow[Trigger Inngest<br/>Payment Check]
TriggerWorkflow --> Redirect[Redirect to Stripe]
Redirect --> Payment{User Pays?}
Payment -->|Success| Webhook[Stripe Webhook<br/>checkout.session.completed]
Webhook --> UpdateDB[Mark Booking as Paid<br/>Clear Payment Link]
UpdateDB --> SendEmail[Inngest: Send<br/>Confirmation Email]
SendEmail --> ShowBooking[Display in My Bookings]
Payment -->|Timeout| Wait10Min[Inngest Waits<br/>10 Minutes]
Wait10Min --> CheckPaid{Booking Paid?}
CheckPaid -->|No| ReleaseSeats[Release Seats<br/>Delete Booking]
CheckPaid -->|Yes| ShowBooking
ReleaseSeats --> End([End])
ShowBooking --> End
style Auth fill:#e1f5ff
style Payment fill:#fff3cd
style Webhook fill:#d4edda
style ReleaseSeats fill:#f8d7da
erDiagram
USER ||--o{ BOOKING : creates
SHOW ||--o{ BOOKING : "has bookings"
MOVIE ||--o{ SHOW : "scheduled in"
USER {
string _id PK "Clerk User ID"
string name
string email
string image
timestamp createdAt
timestamp updatedAt
}
MOVIE {
string _id PK "TMDB Movie ID"
string title
string overview
string poster_path
string backdrop_path
string release_date
string original_language
string tagline
object[] genres "id, name"
object[] casts "id, name, profile_path, character"
number vote_average
number runtime
timestamp createdAt
timestamp updatedAt
}
SHOW {
string _id PK
string movie FK "References Movie._id"
datetime showDateTime
number showPrice
object occupiedSeats "seat_id: user_id mapping"
timestamp createdAt
timestamp updatedAt
}
BOOKING {
string _id PK
string user FK "References User._id"
string show FK "References Show._id"
number amount
string[] bookedSeats
boolean isPaid "Default: false"
string paymentLink
timestamp createdAt
timestamp updatedAt
}
- User β Booking: One-to-Many (A user can have multiple bookings)
- Show β Booking: One-to-Many (A show can have multiple bookings)
- Movie β Show: One-to-Many (A movie can have multiple scheduled shows)
graph TB
subgraph "Frontend Components"
direction TB
subgraph "Pages"
P1[Home]
P2[Movies]
P3[MovieDetails]
P4[SeatLayout]
P5[MyBookings]
P6[Favorite]
P7[Admin/Dashboard]
P8[Admin/AddShows]
P9[Admin/ListShows]
P10[Admin/ListBookings]
end
subgraph "Shared Components"
C1[Navbar]
C2[Footer]
C3[MovieCard]
C4[DateSelect]
C5[Loading]
C6[HeroSection]
C7[FeaturedSection]
C8[TrailersSection]
C9[BlurCircle]
end
subgraph "Admin Components"
AC1[AdminNavbar]
AC2[AdminSidebar]
AC3[Title]
end
subgraph "Context & State"
CTX[AppContext<br/>Global State]
AUTH[Clerk Auth<br/>Provider]
end
end
AUTH --> CTX
CTX --> P1
CTX --> P2
CTX --> P3
CTX --> P4
CTX --> P5
CTX --> P6
CTX --> P7
CTX --> P8
P1 --> C6
P1 --> C7
P1 --> C8
P2 --> C3
P3 --> C3
P3 --> C4
P6 --> C3
C1 --> AUTH
P7 --> AC1
P7 --> AC2
P8 --> AC1
P8 --> AC2
sequenceDiagram
participant Client as React App
participant Clerk as Clerk Auth
participant Express as Express Server
participant Auth as Auth Middleware
participant Controller as Controller
participant DB as MongoDB
participant External as External APIs<br/>(Stripe/TMDB/Redis)
participant Inngest as Inngest Worker
participant Email as Email Service
Note over Client,Email: Example: Create Booking Flow
Client->>Clerk: Get JWT Token
Clerk-->>Client: Return Token
Client->>Express: POST /api/booking/create<br/>Headers: Authorization Bearer {token}
Express->>Auth: Verify Token & Extract userId
Auth-->>Express: userId extracted
Express->>Controller: createBooking(req, res)
Controller->>DB: Check seat availability<br/>Show.findById(showId)
DB-->>Controller: Show data
Controller->>External: Lock seats in Redis<br/>Set temp reservation
External-->>Controller: Seats locked
Controller->>DB: Create Booking document<br/>Booking.create({...})
DB-->>Controller: Booking created
Controller->>DB: Update Show occupiedSeats<br/>Show.save()
DB-->>Controller: Seats marked occupied
Controller->>External: Create Stripe session<br/>stripe.checkout.sessions.create()
External-->>Controller: Session URL
Controller->>DB: Update booking.paymentLink<br/>Booking.save()
DB-->>Controller: Updated
Controller->>Inngest: Trigger app/checkpayment<br/>event with bookingId
Inngest-->>Controller: Event queued
Controller-->>Client: { success: true, url: stripeUrl }
Client->>External: Redirect to Stripe
Note over Inngest,Email: Background Process (10 min delay)
Inngest->>Inngest: Wait 10 minutes
Inngest->>DB: Check Booking.isPaid
alt Booking NOT Paid
Inngest->>DB: Release seats<br/>Delete Booking
DB-->>Inngest: Cleaned up
else Booking Paid
Note over Inngest: No action needed
end
Note over External,Email: Stripe Webhook Flow (on payment)
External->>Express: POST /api/stripe<br/>Webhook: checkout.session.completed
Express->>DB: Update Booking<br/>{ isPaid: true, paymentLink: '' }
DB-->>Express: Updated
Express->>Inngest: Trigger app/show.booked
Inngest-->>Express: Queued
Inngest->>DB: Fetch booking with<br/>populated show & user
DB-->>Inngest: Booking data
Inngest->>Email: Send confirmation email<br/>via Brevo SMTP
Email-->>Inngest: Email sent
Express-->>External: 200 OK
- Movie Discovery: Browse trending movies from TMDB with ratings and details
- Interactive Trailers: Watch movie trailers directly on the platform
- Real-time Seat Selection: Visual seat layout with instant availability updates
- Secure Payments: Stripe integration with automatic checkout sessions
- Booking Management: View all bookings with payment status and ticket details
- Favorites: Save favorite movies for quick access
- Responsive Design: Seamless experience across desktop, tablet, and mobile
- Dashboard Analytics: Revenue, bookings, active shows, and user statistics
- Show Management: Add shows with flexible date-time scheduling from TMDB's now-playing list
- Booking Overview: View all platform bookings with user and payment information
- Show Listings: Monitor all scheduled shows with reserved seats and earnings
- Automated Seat Release: Unpaid bookings auto-expire after 10 minutes using Inngest workflows
- Email Notifications: Brevo SMTP integration for booking confirmations
- Caching Layer: In-memory cache for trending movies and trailers (4-hour TTL)
- Redis Seat Locking: Temporary seat reservations to prevent race conditions
- Webhook Processing: Stripe webhooks for real-time payment verification
- Role-based Access: Clerk metadata for admin authorization
- Framework: React 19.1 with TypeScript
- Build Tool: Vite 7.0
- Styling: TailwindCSS 4.1 (utility-first CSS)
- Authentication: Clerk React SDK
- Routing: React Router DOM v7
- HTTP Client: Axios
- Icons: Lucide React
- Video Player: React Player
- State Management: Context API + Custom Hooks
- Notifications: React Hot Toast
- Runtime: Node.js 20+ with TypeScript
- Framework: Express.js 4.21
- Database: MongoDB (Mongoose ODM)
- Authentication: Clerk Express SDK
- Payments: Stripe API
- Movie Data: TMDB API
- Email Service: Nodemailer + Brevo SMTP
- Background Jobs: Inngest (workflow orchestration)
- Caching: Upstash Redis + In-memory cache
- Deployment: Vercel (serverless functions)
- Package Manager: pnpm
- Code Quality: ESLint + Prettier
- Type Safety: TypeScript 5.9 (strict mode)
- Deployment: Vercel
- Version Control: Git
bookmyshow-clone/
βββ client/ # Frontend React application
β βββ public/
β β βββ screenshots/ # UI screenshots
β βββ src/
β β βββ assets/ # Images, logos, icons
β β βββ components/ # Reusable components
β β β βββ admin/ # Admin-specific components
β β β βββ BlurCircle.tsx
β β β βββ DateSelect.tsx
β β β βββ Footer.tsx
β β β βββ HeroSection.tsx
β β β βββ Loading.tsx
β β β βββ MovieCard.tsx
β β β βββ Navbar.tsx
β β β βββ FeaturedSection.tsx
β β β βββ TrailersSection.tsx
β β βββ context/ # Global state management
β β β βββ AppContext.tsx
β β βββ lib/ # Utility functions
β β β βββ api.ts
β β β βββ dateFormat.ts
β β β βββ isoTimeFormat.ts
β β β βββ kConverter.ts
β β β βββ timeFormat.ts
β β βββ pages/ # Route pages
β β β βββ admin/ # Admin dashboard pages
β β β βββ Favorite.tsx
β β β βββ Home.tsx
β β β βββ MovieDetails.tsx
β β β βββ Movies.tsx
β β β βββ MyBookings.tsx
β β β βββ SeatLayout.tsx
β β βββ types/ # TypeScript interfaces
β β βββ App.tsx
β β βββ main.tsx
β β βββ index.css
β βββ package.json
β βββ tsconfig.json
β βββ vite.config.ts
β βββ vercel.json
β
βββ server/ # Backend Express application
βββ src/
β βββ configs/ # Configuration files
β β βββ db.ts # MongoDB connection
β β βββ nodeMailer.ts # SMTP setup
β β βββ redis.ts # Upstash Redis client
β β βββ validateEnv.ts # Environment validation
β βββ controllers/ # Business logic
β β βββ adminController.ts
β β βββ bookingController.ts
β β βββ showController.ts
β β βββ stripeWebhooks.ts
β β βββ userController.ts
β βββ inngest/ # Background job functions
β β βββ client.ts
β β βββ functions/
β β β βββ booking.email.ts
β β β βββ booking.expire.ts
β β βββ index.ts
β βββ middleware/ # Express middleware
β β βββ asyncHandler.ts
β β βββ auth.ts # Admin authorization
β β βββ errorHandler.ts
β βββ models/ # Mongoose schemas
β β βββ Booking.ts
β β βββ Movie.ts
β β βββ Show.ts
β β βββ User.ts
β βββ routes/ # API routes
β β βββ adminRoutes.ts
β β βββ bookingRoutes.ts
β β βββ showRoutes.ts
β β βββ userRoutes.ts
β βββ types/ # TypeScript types
β βββ utils/ # Helper functions
β β βββ auth.ts
β β βββ cache.ts
β βββ server.ts # Entry point
βββ package.json
βββ tsconfig.json
βββ eslint.config.js
βββ vercel.json
- Node.js 20+ and pnpm installed
- MongoDB Atlas account or local MongoDB instance
- Clerk account for authentication
- Stripe account for payments
- TMDB API access token
- Brevo account for email service
- Upstash Redis account
- Inngest account for background jobs
VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
VITE_BASE_URL=http://localhost:5000
VITE_CURRENCY=βΉ
VITE_TMDB_IMAGE_BASE_URL=https://image.tmdb.org/t/p/w500# Server Configuration
PORT=5000
NODE_ENV=development
CLIENT_URL=http://localhost:5173
# MongoDB
MONGODB_URI=mongodb+srv://username:[email protected]/dbname
# Clerk Authentication
CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxx
CLERK_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
# Stripe Payments
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
# TMDB API
TMDB_ACCESS_TOKEN=eyJhbGciOiJIUzI1NiJ9...
# Email Service (Brevo)
SMTP_USER=your_smtp_user
SMTP_PASS=your_smtp_password
SENDER_EMAIL=[email protected]
# Inngest
INNGEST_EVENT_KEY=your_inngest_event_key
INNGEST_SIGNING_KEY=your_inngest_signing_key
# Upstash Redis
UPSTASH_REDIS_REST_URL=https://your-redis-url.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_redis_token- Clone the repository
git clone https://github.com/yourusername/bookmyshow-clone.git
cd bookmyshow-clone- Install dependencies
# Install server dependencies
cd server
pnpm install
# Install client dependencies
cd ../client
pnpm install- Configure environment variables
# Create .env files from examples
cp server/.env.example server/.env
cp client/.env.example client/.env
# Edit both files with your credentials- Start development servers
# Terminal 1: Start backend (from server/)
pnpm dev
# Terminal 2: Start frontend (from client/)
pnpm dev- Access the application
- Frontend: http://localhost:5173
- Backend API: http://localhost:5000
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/show/trending |
Get trending movies from TMDB |
| GET | /api/show/home-trailers |
Get trailer data for homepage |
| GET | /api/show/all |
Get all available shows |
| GET | /api/show/:movieId |
Get show details for a movie |
| GET | /api/booking/seats/:showId |
Get occupied seats for a show |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/user/bookings |
Get current user's bookings |
| POST | /api/user/update-favorite |
Add/remove favorite movie |
| GET | /api/user/favorites |
Get user's favorite movies |
| POST | /api/booking/create |
Create new booking + Stripe session |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/is-admin |
Verify admin status |
| GET | /api/admin/dashboard |
Get dashboard statistics |
| GET | /api/admin/all-shows |
Get all scheduled shows |
| GET | /api/admin/all-bookings |
Get all platform bookings |
| GET | /api/show/now-playing |
Get TMDB now-playing movies |
| POST | /api/show/add |
Add new show(s) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/stripe |
Stripe payment webhook (raw body) |
| POST | /api/inngest |
Inngest function endpoint |
- Provider: Clerk (OAuth + Email/Password)
- Flow:
- User signs in via Clerk UI
- Clerk issues JWT token
- Frontend includes token in Authorization header
- Backend validates token using
@clerk/expressmiddleware
// Middleware checks Clerk user metadata
const user = await clerkClient.users.getUser(userId);
const role = user.privateMetadata?.role;
if (role !== "admin") {
return res.status(403).json({ message: "Admins only" });
}To create an admin:
- Sign up normally through the app
- In Clerk Dashboard, find the user
- Add to
privateMetadata:{ "role": "admin" }
- User selects seats β
POST /api/booking/create - Backend creates:
- Booking document (isPaid: false)
- Locks seats in
Show.occupiedSeats - Stripe checkout session (30-min expiry)
- User redirected to Stripe β Enters payment details
- On successful payment:
- Stripe sends
checkout.session.completedwebhook - Backend marks booking as paid
- Triggers
app/show.bookedevent β Sends email
- Stripe sends
- On timeout (10 min without payment):
- Inngest
app/checkpaymentworker runs - Releases seats, deletes unpaid booking
- Inngest
Triggered by: app/show.booked Inngest event (after successful payment)
Email includes:
- User name
- Movie title
- Show date & time (Asia/Kolkata timezone)
- Booking confirmation message
Provider: Brevo SMTP via Nodemailer
- User signup/login via Clerk
- Browse movies and view details
- Select show date and time
- Interactive seat selection (max 5 seats)
- Stripe checkout flow (use test card:
4242 4242 4242 4242) - Booking confirmation email received
- View bookings in "My Bookings"
- Admin login and dashboard access
- Add shows from TMDB now-playing
- View all bookings and shows in admin panel
- Verify seat auto-release after 10 minutes (unpaid)
Use Stripe test mode cards:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002 - Any future expiry date, any CVC
Frontend:
// client/vercel.json
{
"rewrites": [{ "source": "/(.*)", "destination": "/" }]
}Backend:
// server/vercel.json
{
"version": 2,
"builds": [{ "src": "src/server.ts", "use": "@vercel/node" }],
"routes": [{ "src": "/(.*)", "dest": "src/server.ts" }]
}Steps:
- Connect GitHub repo to Vercel
- Configure environment variables in Vercel dashboard
- Deploy both
clientandserveras separate projects - Update
VITE_BASE_URLto point to backend URL - Update
CLIENT_URLin backend to frontend URL
- Create cluster on MongoDB Atlas
- Add IP whitelist:
0.0.0.0/0(allow all for serverless) - Create database user with read/write permissions
- Copy connection string to
MONGODB_URI
- Go to Stripe Dashboard β Developers β Webhooks
- Add endpoint:
https://your-backend-url.vercel.app/api/stripe - Select event:
checkout.session.completed - Copy webhook secret to
STRIPE_WEBHOOK_SECRET
- Create account at inngest.com
- Connect Inngest to your backend URL
- Copy signing key to
INNGEST_SIGNING_KEY - Deploy functions:
POST https://your-backend-url.vercel.app/api/inngest
Vikraman R
- GitHub: @VIKRAMANR7
- TMDB for movie data and images
- Clerk for authentication infrastructure
- Stripe for payment processing
- Inngest for background job orchestration
- BookMyShow for design inspiration
β Star this repository if you found it helpful!
Made with β€οΈ and β
