REST API powering real-time event & movie booking with ACID transactions, WebSocket updates, and Redis-backed caching.
Venue is a full-stack booking platform engineered to handle concurrent reservations, real-time seat inventory, and multi-tenant event management — without overselling a single ticket.
Full lifecycle — create events/movies, manage time slots, bookings with seat selection and atomic capacity control.
MongoDB ACID transactions with atomic findOneAndUpdate + $inc prevent double-bookings under concurrent load.
Socket.io delivers instant waitlist notifications — users get promoted the moment a seat frees up.
Cache-aside on all GET endpoints with 60s TTL, automatic invalidation on mutations, fail-open degradation.
Context-aware, Redis-backed — from global DDoS shield (1000/hr) to booking burst guard (5/min).
Full dashboard: event/movie CRUD, slot management with overlap detection, auto-generation, analytics.
| Layer | Technology | Purpose |
|---|---|---|
| Runtime | Node.js Express | HTTP server & routing |
| Database | MongoDB Mongoose | ACID transactions + data layer |
| Cache | Redis ioredis | Caching + rate limit state |
| Real-Time | Socket.io | WebSocket for waitlist events |
| Auth | JWT Argon2id | Stateless auth + secure hashing |
| Validation | Zod | Runtime type-safe validation |
| Storage | Cloudinary | Image uploads (5 MB max) |
| Logging | Winston | Structured logs with redaction |
| Testing | Jest k6 | Unit, integration & load testing |
| Deploy | Docker | Containerized production |
Get up and running in three steps. All endpoints return standardized JSON responses.
curl -X POST {{BASE_URL}}/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe", "email": "john@example.com", "password": "securePass123", "role": "USER" }'
{ "statusCode": 201, "status": "success", "data": { "_id": "65a...", "name": "John Doe", "role": "USER" } }On success, server sets httpOnly cookie token (7‑day). Also supports Authorization: Bearer <token> header.
# Public — no auth required curl {{BASE_URL}}/api/v1/events?page=1&limit=10&sort=-startDate # With filters curl {{BASE_URL}}/api/v1/events?category=Music&price[lte]=500&fields=title,price,location
curl -X POST {{BASE_URL}}/api/v1/bookings \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "slotId": "65a1...", "quantity": 2, "seats": ["A1", "A2"] }'
Auto-waitlisted. You'll get a waitlist:promoted Socket.io event when a seat opens.
curl {{BASE_URL}}/api/v1/bookings/my-bookings \ -H "Authorization: Bearer YOUR_TOKEN"
JWT-based auth with httpOnly cookie transport. Passwords hashed with Argon2id.
| Property | Value |
|---|---|
| Token | JWT (HS256), 7-day expiry |
| Payload | { userId, role } |
| Transport | httpOnly cookie + Bearer header |
| Cookie Flags | httpOnly · secure (prod) · sameSite: none (prod) |
| Hashing | Argon2id — 64 MB, 3 iter, 4 threads |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/v1/auth/register | No | Register. Body: name, email, password, role? |
| POST | /api/v1/auth/login | No | Login. Sets httpOnly cookie. |
| GET | /api/v1/auth/logout | Yes | Clears auth cookie. |
| GET | /api/v1/auth/me | Yes | Current user profile. |
Browse, book, manage favorites, view bookings, update profile.
+ create/edit/publish events & movies, manage slots, analytics.
Full access — all resources and organizer features.
const res = await axios.post('/api/v1/auth/login', { email: 'john@example.com', password: 'securePass123' }, { withCredentials: true }); // Cookie set — all subsequent requests auto-authenticated
s = requests.Session() s.post('https://venue-z8ti.onrender.com/api/v1/auth/login', json={'email': 'john@example.com', 'password': 'securePass123'}) s.get('https://venue-z8ti.onrender.com/api/v1/bookings/my-bookings')
Layered architecture with clear separation of concerns.
// Single atomic op — check + decrement const slot = await Slot.findOneAndUpdate( { _id: slotId, availableSeats: { $gte: quantity } }, { $inc: { availableSeats: -quantity } }, { new: true, session } ); // null → auto-waitlist + emit 'waitlist:added'
The $gte + $inc execute atomically. MongoDB serializes concurrent ops — 100 users, 10 seats, 0 oversells.
Six-tier, Redis-backed system with graceful degradation.
| Tier | Name | Limit | Window | Key | Applies To |
|---|---|---|---|---|---|
| 1 | Global Shield | 1,000 | 1 hr | IP | All auth routes |
| 2 | Auth Brute Force | 10 | 15 min | IP | /auth/* |
| 3 | Scrape Protection | 3,000 | 1 hr | IP | Public GETs |
| 4 | Per-User | 200 | 1 hr | User ID | Auth routes |
| 5 | Write Throttle | 100 | 1 hr | User ID | Mutations |
| 6 | Booking Burst | 5 | 1 min | User ID | Bookings |
| Route | Limiters |
|---|---|
| /api/v1/auth | Auth Brute Force |
| /api/v1/events | Scrape → Write |
| /api/v1/movies | Scrape → Write |
| /api/v1/slots | Scrape → Write |
| /api/v1/bookings | Global → JWT → User → Booking |
| /api/v1/users | Global → JWT → User → Write |
| /api/v1/organizer | Global → JWT → User → Write |
Redis down? Limits silently disabled (passOnStoreError: true). API stays up.
Cache-aside on read endpoints. Auto-invalidation on writes. Fail-open.
| Endpoint | TTL | Key |
|---|---|---|
| GET /api/v1/events | 60s | cache:/api/v1/events |
| GET /api/v1/events/:id | 60s | cache:/api/v1/events/:id |
| GET /api/v1/movies | 60s | cache:/api/v1/movies |
| GET /api/v1/movies/:id | 60s | cache:/api/v1/movies/:id |
Socket.io delivers instant notifications — no polling.
import { io } from 'socket.io-client'; const socket = io('https://venue-z8ti.onrender.com', { transports: ['websocket', 'polling'], withCredentials: true }); socket.emit('join', userId); socket.on('waitlist:promoted', (d) => console.log('Seat!', d.bookingId));
| Event | Direction | Trigger | Payload |
|---|---|---|---|
| join | Client → Server | After login | userId |
| waitlist:added | Server → Client | Slot full | { slotId, quantity, message } |
| waitlist:promoted | Server → Client | Seat freed | { slotId, bookingId, quantity, message } |
Structured responses. Every error includes a requestId for tracing.
{ "statusCode": 200, "status": "success", "data": { ... }, "success": true }{ "status": "fail", "message": "...", "requestId": "uuid" }| Status | Meaning | Cause | Fix |
|---|---|---|---|
| 400 | Bad Request | Validation, invalid ID | Check body/params |
| 401 | Unauthorized | Missing/expired JWT | Login again |
| 403 | Forbidden | Wrong role | Correct account |
| 404 | Not Found | Missing resource | Verify ID |
| 409 | Conflict | Overlap/duplicate | Different slot |
| 429 | Rate Limited | Too many requests | Wait for reset |
| 500 | Server Error | Unhandled | Retry + requestId |
Cause: JWT expired or cookie not sent.
Fix: withCredentials: true. Check domain. Login again.
Fix: Register with "role": "ORGANIZER".
Check RateLimit-Reset header. Wait. Implement backoff.
Slot was full. You're FIFO queued. Listen for waitlist:promoted via Socket.io.
Defense-in-depth: 8 middleware layers on every request.
64 MB, 3 iter, 4 threads — GPU & side-channel resistant.
15+ headers: CSP, HSTS, X-Frame, X-Content-Type…
Recursive sanitize-html — strips all HTML.
mongo-sanitize strips $ and . operators.
Immune to XSS token theft. Secure + SameSite in prod.
Winston redacts password, token, auth, cookie fields.
| # | Layer | Purpose |
|---|---|---|
| 1 | Helmet | Security headers |
| 2 | Request ID | UUID v4 tracing |
| 3 | Morgan → Winston | Structured logging + redaction |
| 4 | CORS | Origin whitelist, credentials |
| 5 | JSON Parser | Body parsing |
| 6 | Cookie Parser | httpOnly cookies |
| 7 | sanitize-html | XSS prevention |
| 8 | mongo-sanitize | NoSQL injection |
| Setting | Value |
|---|---|
| Storage | Cloudinary |
| Formats | jpg, jpeg, png, gif |
| Max Size | 5 MB |
| Validation | Extension regex + MIME |
Full interactive API reference. All endpoints listed below — click "Authorize" to enter your Bearer token, then "Try it out" on any endpoint to test live.