Eventora-MERN: The Booking App That Tames AI, Payments, and Seats
A close look at a full-stack event platform that uses context-injected Gemini prompts, HMAC-verified Razorpay payments, OTP-based access, and deterministic seat assignment to feel more like a product than a tutorial.
- Eventora’s most interesting move is not adding AI, but constraining it to live MongoDB event context so recommendations stay inside the app’s inventory.
- The booking flow is built as a trust chain, with Razorpay verification happening before seats are decremented and bookings are confirmed.
- OTP is treated as shared infrastructure across verification, login, and booking, which makes authentication feel like a platform layer instead of a one-off feature.
- Small implementation choices, from native crypto to deterministic seat numbers, make the app feel operationally deliberate rather than demo-driven.
Most event apps are just CRUD with a checkout button. Eventora-MERN is more interesting because it keeps putting guardrails around every step that could otherwise drift into guesswork, fraud, or vague state.
That matters because the repo is not trying to impress with breadth. It tries to make a simple event platform feel trustworthy. The AI assistant only speaks from current event data. Payments are not marked complete until the signature checks out. Seats are not abstract counts, they are assigned objects.
The clever part is not the stack. It is the constraints.
The standout feature is the chat assistant in server/ai/aiController.js. It does not rely on a vector database or a heavy retrieval pipeline. It queries upcoming events from MongoDB, formats them into context, and injects that context directly into the Gemini prompt.
That is a small design choice with a big effect. The model is told to recommend only events that exist in the supplied context, which narrows the answer space and makes the feature useful for a live inventory problem instead of a generic chatbot demo.
A RAG-lite assistant built from live event data
The flow is simple enough to read in one sitting. The controller fetches relevant events, builds a text block, appends the user’s question, and sends the whole bundle to Gemini 1.5 Flash. The choice of the Flash model fits the UX goal: quick answers, not long-form analysis.
const upcomingEvents = await Event.find({ date: { $gte: new Date() } });
const eventContext = upcomingEvents
.map(event => `${event.title} | ${event.category} | ${event.date}`)
.join('\n');
const prompt = `
You are Eventora's assistant.
Only recommend events that exist in the context provided.
Context:
${eventContext}
User question: ${message}
`;
const result = await model.generateContent(prompt);
The important part is not the prompt syntax. It is the boundary. Eventora uses the database as a live source of truth, then deliberately shrinks the model’s freedom so the answer stays useful.
The booking flow is a trust chain, not a single action
The payment path is similarly careful. The server creates a Razorpay order, the client handles the checkout, and the backend verifies the returned signature with native crypto.createHmac('sha256', ...). Only after that does the system confirm the booking and reduce available seats.
| Generic event app | Eventora-MERN |
|---|---|
| Booking can feel confirmed as soon as the UI says so. | Booking is confirmed only after HMAC payment verification succeeds. |
| AI chatbot answers broadly from model memory. | AI answers only from event context pulled from MongoDB. |
| Seats are usually just a counter. | Seats are assigned deterministically as visible seat numbers. |
That sequence matters because it turns the checkout flow into a chain of dependent facts. Payment is not a vibe. It is a verified state transition.
OTPs do more than log users in
The auth layer is broader than a basic email-and-password setup. Users begin as unverified, OTPs are used for account verification, login, and even booking, and the middleware splits access into protect and admin gates. That makes OTP a shared primitive, not a one-off feature.
if (!user.isVerified) {
throw new Error('Please verify your account');
}
const otp = generateOTP();
await OTP.create({
email: user.email,
code: otp,
type: 'event_booking'
});
There is a product lesson here. Shared verification logic reduces drift. If the same mechanism handles onboarding, login, and booking, the platform gets easier to reason about and harder to misuse.
Seat numbers turn inventory into a physical experience
The seat assignment logic is small, but it changes the feel of the product. Instead of decrementing a generic count, the backend computes a seat number from the remaining inventory. That makes the booking feel closer to a venue map than a shopping cart.
const bookedCount = totalSeats - availableSeats;
const row = String.fromCharCode(65 + Math.floor(bookedCount / 10));
const number = (bookedCount % 10) + 1;
const seatNumber = `${row}${number}`;
The result is a tiny bit of theater. A user does not just buy access. They get a place in a room.
The backend choices are boring in the best way
Eventora also shows a practical bias in the supporting code. Brevo is called directly through HTTP, the dev workflow uses concurrently, and the codebase stays close to a controller-service-route structure. None of that is flashy. All of it reduces friction.
| Choice | Why it helps |
|---|---|
| Native crypto for signature checks | Fewer dependencies, clear verification path. |
| Direct Brevo API calls | Simple email integration without extra abstraction. |
| Concurrently for local dev | One command to run client and server together. |
| Controller-service-route split | Easier to trace where behavior lives. |
That is why the repo feels more product-shaped than many tutorial projects. It does not chase novelty in the stack. It chooses familiar tools and then uses them with discipline.
What Eventora is really teaching
The real lesson is that small platforms become compelling when the hard parts are stitched together cleanly. Eventora does not just book events. It shows how AI, payments, auth, and inventory can be constrained so the whole system behaves predictably.
That is the difference between a demo and a service. A demo shows features. A service shows rules.