Engineering
How it’s built
A production ticketing and marketplace platform. Here is what it runs on, how it is tested, and what was measured along the way.
- 446automated tests, all passing
- 52features you can try
- 55pages
- 76API routes
- €0held by the platform, ever
Stack
Boring, dependable tools
- Next.js 15 (App Router)Server-rendered pages, so a listing loads fast on a phone and reads well to Google.
- React 19 + TypeScriptStrict types end to end; `tsc --noEmit` is part of every change.
- SQLite via better-sqlite3One file, synchronous transactions — overselling is blocked atomically, backups are a file copy.
- sharpEvery photo is re-encoded and cut to the exact shape of each slot it appears in.
- nodemailer over SMTP (SendGrid)Tickets, payment instructions, reminders and security alerts.
- PayPal & Stripe REST, plain fetchNo SDKs — small clients calling each seller’s own account directly.
- qrcodeQR tickets, collection codes, GiroCode payment QR and the WhatsApp poster.
- Gemini + ClaudeVisitor Q&A, the help bot, the AI page designer and voice input — each with fallbacks.
Eight runtime dependencies in total. No ORM, no payment SDKs, no CSS framework.
Architecture
One machine for events and shops
A ticket and a plate of momo are the same thing to this system: a listing (a page), priced types with caps (tiers or items), orders, a QR code per unit and one scan to use it. Events and shops share every line of that — only the words change.
The platform is never on the money path: it holds no funds and no card data.
- Multi-tenant. Every portal and API request checks that the organizer owns the event, order or photo it touches — enforced on the server, covered by tenancy tests.
- Money never touches the platform. Each seller connects their own IBAN, PayPal or Stripe; buyers pay them directly. The platform is software, not a payment service.
- Server-side truth. Prices, caps and seats are enforced inside a database transaction, so a browser cannot change a price and a rush cannot oversell.
Reliability & testing
446 tests, and a check that they bite
automated tests on Node’s built-in test runner, all passing: money maths, orders and capacity, payouts, promos and referrals, reviews, tenancy, consent, email, SEO.
A mutation audit injected 19 deliberate bugs into the code; the suite caught 14. Each of the 5 that slipped through then got its own test.
Performance
Measured before and after
| What | Before | After |
|---|---|---|
| Homepage — SQL statements per request | 96 | 9 |
| Phone homepage — autoplay video downloaded on load | 10.5 MB | 0 bytes |
| Item page — image weight | 592 KB | 117 KB |
| Returning-buyer query (was quadratic) | 1,026 ms | 5 ms |
| Portal dashboard, 20 events — SQL statements | 243 | 4 |
| Monthly statement — SQL statements | 1,988 | 3 |
Security
Built for other people’s money
- Two-factor sign-in for organizers: an authenticator app, or a code emailed to the account’s own address.
- Changing where money lands asks the organizer to prove it is them again, takes effect only after 24 hours, and sends an alarm email.
- Sellers’ own PayPal secrets and Stripe keys are encrypted at rest (AES-256-GCM).
- Rate limits on sign-in, code checks and order creation.
- Uploads are checked by their real bytes (magic numbers), not their file name — an SVG disguised as a PNG is refused.
- Request bodies are capped while they stream in, so an oversized POST is cut off before it is buffered.
- Door scanner links are signed and time-limited, and open one event’s check-in and nothing else.
- A tenant-isolation review of every portal route: 9 findings, all fixed, with tests that keep them fixed.
- A live-exploitable leak of account data on order pages was found and closed with signed per-order access tokens.
Privacy & compliance
Keeps less, for less time
- GDPR retention: buyer names and emails are overwritten after the event; counts and revenue survive, the person does not.
- Erasure on request, including customer cards and marketing consent.
- Marketing is double opt-in only, and every announcement has a one-click unsubscribe.
- A consent banner is answered before any analytics loads — and analytics never runs on pages whose URL carries a credential.
- Listing pages name the organizer or shop as the one selling (“Presented by”, “Sold by”), so the seller of record is clear under German VAT rules.
AI
Helpful, and allowed to say “I don’t know”
- Grounded: the event assistant answers only from the organizer’s own page content.
- Strict JSON out, validated before use — a malformed reply is treated as no reply.
- A confidence gate: when the model is not sure, the question goes to the organizer instead of being guessed.
- A model ladder with fallbacks (Gemini, then Claude), and built-in answers when no model is reachable.
- The page designer can only choose from the site’s fixed design options — it never writes code or styles into a page.
Want something like this for your restaurant, shop or events?
Tell us what you run and what gets in the way. A real person reads every message.