TktPlz is a full-stack TypeScript-compatible JavaScript application split into a React frontend and an Express backend. The two communicate over a REST API and a persistent Socket.IO connection. A Redis-backed queue layer (BullMQ) handles time-sensitive background jobs — mainly seat unlocking and post-event cleanup — so the HTTP layer stays fast and stateless.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Sumitbose5/tktplz/llms.txt
Use this file to discover all available pages before exploring further.
Tech stack at a glance
Frontend
React 19, Vite, TailwindCSS 4, Tanstack Query, Zustand, Socket.IO client, React Hook Form, React Router DOM 7
Backend
Node.js, Express 5, Passport.js, JWT, cookie-parser, CORS, Multer, date-fns
Relational database
MySQL via Drizzle ORM — events, seats, tickets, users, organisers, bookings, payouts, refunds, and more
Document database
MongoDB via Mongoose — stores unstructured or variable-schema data such as event chatroom messages and registration field responses
Real-time layer
Socket.IO 4 on the server, socket.io-client 4 on the browser, with Redis (ioredis) as the shared state store for seat locks
Queue system
BullMQ with Redis — two workers:
unlockWorker releases held seats and tickets on timeout; eventCleanupWorker finalises events and creates payout recordsPayments
Razorpay — order creation, HMAC payment verification, refund initiation, and refund webhook handling
Auth
Passport.js with
passport-google-oauth20 and passport-jwt; OTP generation via otp-generator; TOTP for admin accounts via speakeasy; JWT stored in HTTP-only cookiesFile and media
Cloudinary via the
cloudinary SDK and streamifier for streaming uploads; Multer handles multipart form data before the stream is forwarded to CloudinaryResend for transactional email (OTP delivery, invite links, payout receipts); Nodemailer as an alternative transport
PDF and QR generation
qrcode generates QR data server-side; pdfkit and puppeteer produce printable ticket and receipt PDFsDeployment
Frontend on Vercel (
tktplz-05.vercel.app / tktplz.me); backend on Render; static assets via Cloudinary CDNHow the layers fit together
Frontend
The frontend is a single-page application built with React 19 and bundled by Vite. Routing is handled by React Router DOM 7. All server state — event listings, booking summaries, ticket details — is fetched and cached by Tanstack Query, which provides automatic background refetching and stale-while-revalidate behaviour without manual cache management. Client state that needs to persist across components (the authenticated user object, modal visibility, and the stored redirect URL) is managed with Zustand stores. UI is built with TailwindCSS 4 and MUI components, with charts rendered by Recharts in the organizer dashboard. The frontend connects to the backend Socket.IO server on page load. When a user opens a seating event, the client emits ajoin event with the eventId to subscribe to that event’s room. Live seat-lock updates from other users arrive as seats-unlocked and bookingUpdate socket events, keeping the seat map accurate without polling.
Backend
The backend is an Express 5 application running on Node.js with ES modules ("type": "module"). It listens on port 5000 by default and exposes the following route groups:
| Prefix | Purpose |
|---|---|
/api/auth | Registration, OTP, Google OAuth, JWT verification, admin login |
/api/event | Event CRUD, poster upload, seat/ticket queries, event types |
/api/booking | Booking summary, seat locking and unlocking |
/api/payment | Razorpay order creation, payment verification, refunds, payouts |
/api/ticket | Ticket retrieval and QR validation |
/api/organizer | Organizer account management |
/api/admin | Admin invite links, TOTP QR codes, payout management |
/api/user | User profile and booking history |
/api/halls | Hall and screen availability for seating events |
http.Server instance so Socket.IO can share the same port. The io instance is attached to the Express app object (app.set('io', io)) and is also exported as getIO() for use inside BullMQ workers that need to emit socket events after completing a job.
Databases
MySQL (Drizzle ORM) is the primary relational store. The schema is split across individual files and re-exported fromdrizzle/schema.js:
userSchema,organiserSchema,adminSchema— accounts and roleseventSchema,hallSchema,screenSchema,seatSchema,zoneSchema— event structure and venue layoutticketSchema,ticketPrices,ticket_student,bookingsSchema— booking and ticket recordsparticipantInfo— registration-event attendee datapayoutSchema,refundSchema— financial records created after event completion or cancellationissueSchema,likesSchema,inviteLinkSchema— support, engagement, and invite features
drizzle-kit generate / drizzle-kit migrate).
MongoDB (Mongoose) supplements MySQL for data that benefits from a flexible document structure, such as in-event chatroom messages and dynamic registration field responses. The connection is established at startup via dbConnect().
Real-time layer
Socket.IO runs on the same HTTP server as Express. ThesocketHandler utility wires up four socket events:
join— subscribes a client socket to a named event room (keyed byeventId)leave— unsubscribes from a roombookingUpdate— broadcasts a booking update to all connected clientsdisconnect— cleans up on client close
unlockWorker BullMQ job calls getIO() and emits seats-unlocked or tickets-unlocked to the relevant event room. Every browser watching that event receives the update and re-renders the seat map immediately — no polling required.
Redis (via ioredis) acts as the shared state layer for seat locks. A lock is written to Redis when a user selects a seat; the BullMQ job is scheduled to delete it after the lock duration expires.
Queue system
Two BullMQ workers run in the same Node.js process as the Express server, initialised by importingworkers/index.js at the top of app.js:
unlockWorker — processes jobs from the unlockJob queue. Each job carries a type (seat or ticket), an eventId, an itemId, a userId, and an optional count. The worker calls the appropriate unlock helper, then emits a socket event to notify connected clients.
eventCleanupWorker — processes jobs from the eventCleanup queue. When an event ends, this worker marks the event as completed, frees the venue screen (for Seating events), aggregates ticket revenue, and inserts a payout record so the organizer can request their earnings.
Both workers connect to Redis directly and handle SIGTERM / SIGINT for graceful shutdown.
Authentication flow
- Email + OTP:
POST /api/auth/user-reg(oruser-login) creates or retrieves the user, generates a 6-digit OTP withotp-generator, and sends it via Resend.POST /api/auth/verify-otpvalidates the code and issues a JWT stored in an HTTP-only cookie. - Google OAuth:
GET /api/auth/googleredirects to Google. The callback atGET /api/auth/google/callbackis handled bypassport-google-oauth20, which upserts the user record and issues the same JWT cookie. - Admin TOTP: Admin accounts use time-based one-time passwords via
speakeasy. Invite links carry a QR code (GET /api/auth/admin/invite/:email) that the admin scans with any TOTP app. - Route protection: The
authmiddleware (passport-jwt) validates the JWT on every protected route.isOrganiserandisAdminrole-guard middleware run afterauth.
Payment flow
/api/payment/payout/*, accessible only to admins and organisers depending on the action.
The base URL for all API requests is the backend server root. In production this is the Render deployment URL; in local development it defaults to
http://localhost:5000. The frontend reads this from the VITE_BASE_URL environment variable.