Skip to content
G
All work
Full-stack platform

MartialXPro

A tournament management platform for martial arts organisations — and the first system where I was responsible for every layer, from the schema to the server it runs on.

martialxpro.com
Role
Co-Founder & Technical Architect
Period
2026 — Present
Scope
Backend · Database · Infrastructure
Status
Live in production
On this page7 sections
01

The problem

Martial arts organisations run tournaments on spreadsheets, message groups and paper. A single multi-day event means collecting registrations, splitting competitors into categories by age, weight and belt, drawing brackets, tracking results, and then producing certificates and ID cards for everyone involved. Every one of those steps is manual, and every one of them is a place where an event loses an afternoon to an error.

MartialXPro replaces that with one system covering the three people who actually touch a tournament: the organisation running the event, the organiser operating it, and the player registering for it.

02

System architecture

The deployment is deliberately simple: a single Ubuntu host running Nginx as a reverse proxy in front of a Node.js and Express API supervised by PM2, talking to PostgreSQL. No orchestration layer, because at this stage it would be complexity without a reason — and because a stack I fully understand is one I can fix at 2am.

UBUNTU SERVERClientorganiseradmin · playerHTTPSNginxreverse proxyTLS · static · routingproxy_passExpress API150+ REST endpointsJWT → RBAC middlewaresupervised by PM2PrismaPostgreSQLrelational schemamigrationsSSH key access only · config via environment variables · PM2 restarts the API on boot
The path a request takes. Every box on the Ubuntu side is one I configured and maintain.

Configuration is entirely environment-variable driven, so nothing environment-specific and no credential lives in the repository. Server access is SSH-key only.

03

Data model

The schema is relational and modelled around ownership: organisations own tournaments, tournaments contain categories, and registrations connect players to the categories they compete in. Getting those relationships right early mattered more than anything else in the project — fixture generation, reporting and the analytics dashboard are all downstream of the schema, and a weak model would have shown up in all three.

Schema changes are managed as Prisma migrations, versioned alongside the code, so the database state is reproducible rather than hand-edited.

04

Authentication and access control

Sessions are JWT-based, and authorisation is role-based access control across three roles:

  • Admin
    Platform-level control across organisations.
  • Organiser
    Runs tournaments for their own organisation.
  • Player
    Registers and competes.

RBAC is enforced in middleware rather than checked inside each handler, so access rules are declared per route instead of being re-implemented — and re-implemented slightly differently — across 150+ endpoints. That decision came directly from the pentesting side of my background: scattered authorisation checks are where broken access control lives.

05

What the platform does

Shipped and in use:

Organisation managementTournament managementPlayer registrationTournament registrationCategory managementAutomated fixture generationCertificate generationPlayer ID cardsReportsAnalytics dashboard

Designed, build in progress:

Subscription plansPayment workflow
06

Deployment and operations

The application runs on an Ubuntu server I provision and administer. PM2 supervises the Node process and restarts it on boot and on failure; Nginx terminates incoming traffic and proxies to the API. Access is over SSH keys, configuration comes from environment variables, and problems get diagnosed from PM2 and Nginx logs on the live host.

AWS infrastructure — EC2, VPC, IAM and S3 — is planned and provisioned for the scale-out path, informed by the hands-on labs work in the next case study.

07

What broke, and what it taught me

The honest part. These are the problems that cost the most time, and they're the reason I'd back myself to operate a production system.

  1. Prisma migrations drifting from production

    Schema changes that applied cleanly in development did not always land cleanly against a database holding real tournament data. Resolving migration state without dropping and reseeding the production database — and changing how I sequenced schema changes afterwards — was the single most instructive part of the project.

  2. Authentication and role resolution edge cases

    With three role types and organisations owning their own tournaments, 'is this user allowed to do this' is not a single check. Token handling, role resolution and ownership checks all had to agree, and the failures were the quiet kind that return the wrong data rather than an error.

  3. PostgreSQL behaviour underneath the ORM

    The ORM hides the database until it doesn't. Connection handling and query behaviour on relational reads had to be understood at the SQL level, not just the Prisma level, before some issues made sense.

  4. Linux permissions between deploy user and process user

    The account that deploys the code and the account that runs it are not the same, and file ownership, log directories and environment file access all have to reflect that. Classic Linux administration, and it only surfaces in production.

  5. Nginx reverse proxy configuration

    Getting upstream routing, forwarded headers and the split between proxied API traffic and static content correct — so the application sees the request it expects rather than the one Nginx rewrote.

  6. Debugging against a live server

    No local reproduction, no second chance. Reading PM2 and Nginx logs on a running system and reasoning from them is a different skill from debugging locally, and it's the one that made me confident operating production.

navigate open