Full-stack case study

Tournwa

The backend powering Tournwa, a UAE sports platform — a four-format competition engine from seeding to league rankings, Stripe Connect entry fees, and registration for athletes solo, as teams or as doubles partners.

Role
Lead Backend Developer
Duration
8 months — ongoing
Year
2026
Stack
Node.js · Express.js · PostgreSQL · Redis · Stripe
Tournwa organizer tournament desk — bracket view with participants, scores, categories and tournament progress

01 / The Problem

Real competition needs more than a signup form.

Organizers running structured sports competitions need categories, seeding, group stages, knockout brackets, per-set scoring and league points — plus entry fees, approvals, capacity limits and parent consent for minors. Athletes need to find events and register solo, as a team, or with a doubles partner. No spreadsheet survives that.

Target users

Three roles: organizers — clubs and academies running tournaments, trainings and pickup games; athletes, including minors behind parent-consent flows; and platform admins.

02 / The Solution

One backend, three apps.

A single Express API serving three surfaces — an athlete mobile app, an organizer web app and an admin panel — through 338 endpoints across 31 route groups. The competition engine carries a category from seeding through groups and brackets to point settlement; Stripe Connect routes entry fees to premium organizers with the platform's per-event fee taken automatically; and five background queues keep SMS, email and push notifications out of the request path.

Why this stack

  • PostgreSQL — partial unique indexes, row locks and transactions guard money
  • Redis — cache, JWT blacklist, OTP store and rate limits in one piece
  • BullMQ — five queues keep Twilio, SMTP and Expo push off the request path
  • Node cluster — multi-core workers with connection pools budgeted per worker

03 / Features

What the platform does.

Auth, Four Ways

Email, Google, Apple and phone OTP with a Redis token blacklist

Competition Engine

Seeding, groups, brackets, per-set scoring and tie-broken standings

Stripe Connect

Destination charges with per-event platform fees and payouts

Flexible Registration

Solo, team or doubles — with parent consent gating for minors

League Points

Placement-to-points models settled into an append-only ledger

Organizer Spaces

Private communities that never leak into public listings

04 / Technical Challenges

The hard parts — and how they fell.

A four-format competition engine

Single elimination, single- and multi-group round robin, and two-stage groups-to-knockout — with byes, seeding methods, third-place matches, and rules that must freeze the moment play starts.

How I solved it

Brackets pad to the next power of two, order seeds with a recursive 1-vs-N mirror and pre-wire winner/loser edges, so completing a match propagates results mechanically; byes auto-complete at generation. Groups distribute serpentine-style and schedule by the circle method. Guards enforce forward-only match statuses and freeze competition settings once seeding exists — every mutation transactional, cache invalidation deferred to after commit.

Race-proof payments

Finalization can arrive twice — webhook and client poll — users double-submit intents, and a payment must never double-approve a registration or double-reserve a capacity slot.

How I solved it

Two partial unique indexes allow exactly one active and one completed payment per registration; finalization takes a row lock, early-returns if already done, then approves the registration, reserves the slot and activates participants in one transaction. Stripe idempotency keys derive from internal IDs, and receipt emails are double-guarded by deduped queue job IDs plus a null-check update.

Points that settle exactly once

When a tournament completes, placements must convert to league points exactly once per category even on retries — and stay correctable when organizers later edit points models.

How I solved it

Settlement derives placements per format and skips any category that already has a ledger entry for the tournament. Corrections diff the desired totals against the ledger sum and write compensating rows — history is never edited or deleted. Leaderboards rank in SQL with tie sharing behind a version-keyed five-minute cache.

05 / Architecture

How the system fits together.

THREE CLIENT APPS

athlete mobile · organizer web · admin

EXPRESS 5 API

338 endpoints · JWT + RBAC · cluster

POSTGRESQL

~60 models · transactions · row locks

STRIPE CONNECT

entry fees & payouts

REDIS + BULLMQ

cache · OTP · five queues

TWILIO · SMTP · EXPO

sms · email · push

CLOUDFLARE R2

presigned media uploads

External services

06 / Screenshots

The product, up close.

Tournwa organizer tournament desk — bracket view with participants, scores, categories and tournament progress
Desktop
Mobile

07 / Results

What it moved in the real world.

API endpoints
0
Automated test cases
0
Database models
0+
Background queues
0

One API in production behind an athlete mobile app, an organizer web app and an admin panel. Organizers run complete competitions — seeding, groups, brackets, scoring, rankings — while entry fees settle through Stripe Connect with the platform fee taken automatically per event. The money paths and the engine's golden paths are covered by a 29-file Jest suite.

08 / Tech Stack

Built with.

Frameworks

  • Node.js
  • Express 5
  • Sequelize
  • Jest

Libraries

  • BullMQ
  • Stripe
  • ioredis
  • Joi
  • Twilio
  • Expo Push

Infrastructure

  • PostgreSQL
  • Redis
  • Railway
  • Cloudflare R2
  • Stripe Connect

09 / My Contribution

What I built, specifically.