<- cd ~/home
$ cat ./projects/boka.md

Boka Ticketing

NEXT.JSEXPOPAYMENTS
ROLE
Full-Stack Developer
YEAR
2026
TYPE
Ticketing Platform
STATUS
LIVE
TICKETS SOLD
1,965
Sold through the platform since launch.
LOAD TESTED
500
Concurrent users on public pages under k6 — p95 under 2s, under 5% failures.
APPS ON ONE API
3
Customer web, Sunmi POS, and gate scanner.
Boka TicketingDrop main screenshot
// 01

OVERVIEW

Boka Ticket is an online ticketing platform for discovering and booking events and experiences — browse what is on, check the details, and buy online, or walk up and buy at the door through the POS app.

// 02

THE PROBLEM

The event industry is fragmented. People find it hard to discover and book events, and organizers find it hard to reach and manage their audience. Boka Ticket brings discovery and booking into one accessible platform.

Boka Ticketing screenshot 1Screenshot 1
Boka Ticketing screenshot 2Screenshot 2
Boka Ticketing screenshot 3Screenshot 3
Boka Ticketing screenshot 4Screenshot 4
Boka Ticketing screenshot 5Screenshot 5
// 03

THE SOLUTION

One platform covering the whole ticket lifecycle — listing, booking, payment, on-site sales, and validation at the door — on a single database and a single API serving three apps: a customer site carrying the admin and organizer dashboards, a POS app running on Sunmi terminals, and a scanner for the gate.

All three read and write the same data through that one API. The interface is Arabic-first and RTL with an English toggle, and every session and schedule calculation runs in Saudi time.

The booking engine exists to stop one seat selling twice under load, and it takes three layers to do it. Inventory rows are locked inside a database transaction; seats are decremented at the moment of the lock rather than when payment clears; and a Redis hold expires after fifteen minutes, returning the seats if the purchase never completes. The payment gateway can also call back twice for the same purchase — once from the buyer’s browser, once from the bank’s own server — so confirmation is idempotent behind a unique booking reference. No duplicate booking, no seats deducted twice.

Every sale produces a ZATCA-compliant simplified tax invoice carrying its QR code, emailed to the buyer with a separate QR ticket for each attendee. The scanner validates those tickets against the server, never on the device. Events can run timed sessions whose capacity is shared across ticket tiers, with discount codes, commission-bearing group bookings, and cash, card, or split payment at the POS with shift close-out. Access is RBAC across five roles, each with its own user table, cookie, and token verifier rather than a flag on one shared account: POS and scanner staff are scoped to a single event, and an organizer sees their own numbers with no administrative access at all.

It runs on Docker Swarm through Dokploy and redeploys on every push. The image is multi-stage and ships Chromium with Arabic fonts so ZATCA invoices render correctly; migrations apply on boot, and the health check queries PostgreSQL and Redis rather than just answering 200. Invoice rendering is heavy — seconds of work and hundreds of megabytes of Chromium per render — so it moved onto a BullMQ queue with a worker inside each replica, keeping the payment path clear of it and capping concurrent renders explicitly. Separately, the public pages are load-tested with k6 at 500 concurrent users against thresholds of p95 under two seconds and a failure rate below five percent.

FEATURES

  • Three apps, one API
  • Row-locked booking engine
  • 15-minute Redis hold
  • Idempotent payment callback
  • ZATCA invoice with QR
  • Server-side ticket validation
  • Shared-capacity sessions
  • Five-role RBAC
  • Auto-deploy on push

STACK

  • Next.js 15
  • Node 22
  • TypeScript
  • Expo
  • PostgreSQL 16
  • Redis 7
  • Prisma Migrations
  • BullMQ
  • Docker
  • Docker Swarm
  • Dokploy
  • nginx
  • Let's Encrypt
  • k6

OUTCOMES

  • 1,965 tickets sold through the platform (as of August 2026)
  • Public pages hold 500 concurrent users at p95 under 2s (k6)
  • Invoice rendering kept off the payment path
  • Every push redeploys the stack unattended