Case study 01 · Community, commerce and realtime platform

Zone 32-19

A community, commerce and lifestyle platform I build alone, from the Next.js client through an Express and PostgreSQL core to realtime calls, payments and an internal employee portal.

Status: Live, pre-launchDeployed on Railway, public launch pending
The Zone 32-19 logo, a bone-white Z with a small purple triangle, above the words ZONE 32-19 on a near-black background.
Role
Founder-engineer and sole developer across every repository
Timeline
Feb 2025 to present
Context
Own product, private repositories
Layers
Web · Backend · Realtime
Links
zone-32-19.com (opens in a new tab)
Private repositories (backend, frontend, database, gateway)

01Overview

Zone 32-19 is a hybrid of community platform, online store and lifestyle brand, organised around eight identity niches such as gaming, cars, bikes, fitness and anime. Members get profiles, messaging, calls and events; the business side gets commerce, subscriptions, digital products and an internal portal for staff.

Why it exists. It is my own product and the system where I learned to own everything: schema, API, realtime layer, payments, security, deployment and interface. There is no team behind it. Every repository in the platform is written and maintained by me.

What it shows. Not a landing page with a checkout. The backend alone has 39 domain modules, two database schemas and a finance ledger, and it is designed to run as several replicas on Railway.

Specification

Frontend
Next.js 16 App Router, React 19, strict TypeScript, Tailwind v4
Backend
Node.js and Express, 39 domain modules, raw parameterised SQL
Database
PostgreSQL, two schemas, ~426 tables, 122 versioned migrations
Realtime
Socket.IO with Redis adapter, WebRTC 1:1 audio and video
Payments
Stripe PaymentIntents, subscriptions, idempotent webhooks
Jobs
BullMQ for finance jobs, node-cron behind an environment gate
Security
HttpOnly JWT sessions, TOTP, Google OAuth, CSRF, audit log
Hosting
Railway; Caddy + Coraza WAF built for the edge
  • Two identities in one database. Public users and employees live in separate schemas with separate sessions, MFA and audit trails.
  • Payments resolve to exactly one effect, even under Stripe retries and multiple replicas.
  • Built to run as several web replicas. Redis carries sockets, presence and rate limits; scheduled jobs run only where an environment gate allows.
  • The browser only ever talks to its own origin, so the session cookie stays first-party and HttpOnly.

02What I built

Identity and security

  • JWT sessions in HttpOnly cookies, checked against a sessions table on every request so they can be revoked server-side.
  • TOTP two-factor authentication, emailed codes, trusted devices, step-up for money-moving routes, and Google sign-in.
  • A separate employee portal identity with its own cookie, double-submit CSRF tokens and 37 permission keys.
  • Helmet CSP and HSTS, a CORS allow-list, Redis-backed rate limits keyed on a trusted client IP, and a mutation audit log with secrets scrubbed.

Realtime and social

  • Socket.IO authenticated from the session cookie, with a Redis adapter so events reach users connected to any replica.
  • Presence, typing indicators, read receipts, reactions, edits, deletions and per-user notification rooms.
  • 1:1 audio and video calls over WebRTC, with Socket.IO signalling, invite timeouts and state that expires on its own.
  • PostgreSQL LISTEN/NOTIFY bridges that push moderation-queue and niche changes to connected clients without duplicate events.

Commerce and finance

  • Backend modules for the store, cart, orders, drops, gift cards, mystery boxes and digital products with single-use download tokens.
  • Stripe PaymentElement checkout against a server-priced PaymentIntent. The browser never decides the amount.
  • Signature-verified webhooks, deduplicated inside the same transaction as their side effects.
  • A finance hub with double-entry journal posting and reconciliation, scheduled ingestion from Stripe, Revolut and Yapily, and BullMQ jobs with retries for manual syncs and payout webhooks.

Frontend and operations

  • A Next.js 16 client with a same-origin BFF proxy, route groups for public, member and employee areas, and edge middleware gating.
  • A 54-page employee portal covering HR, leave, finance, legal policy versions, commerce configuration and sessions.
  • A GSAP-driven landing for eight niche worlds, with motion tiers that respect reduced-motion and save-data preferences.
  • Health probes that return 503 when dependencies degrade, graceful shutdown, Sentry, request IDs and CI checks for migrations and secrets.

03Architecture

A modular monolith rather than microservices. One Express API holds 39 domain modules behind a single middleware pipeline, PostgreSQL holds two isolated schemas, and Redis is the shared state that lets the web tier run as several replicas.

Fig. 01.1

Platform topology on Railway

Edge

  • ClientBrowser

    Next.js 16 app, Socket.IO client, WebRTC peer media

    • connects to Next.js BFF · HTTPS
    • connects to WAF gateway
    • connects to Socket.IO server · WebSocket
  • GatewayNext.js BFF

    /api/proxy on the same origin; forwards cookies and XFF chain

    • connects to Express API · private network
  • Built, not liveWAF gateway

    Caddy, Coraza, OWASP CRS

    • connects to Express API

Application

  • ServiceExpress API

    39 domain modules behind one pipeline: rate limits, CSRF, mutation audit

    • connects to PostgreSQL
    • connects to Redis
  • ServiceSocket.IO server

    presence, typing, notifications, call signals

    • connects to Redis · pub/sub
  • In the API processScheduled jobs

    node-cron in process, CRON_ENABLED gate

    • connects to Revolut, Yapily · scheduled sync

State

  • StorePostgreSQL

    public + employee_portal, 122 SQL migrations

  • StoreRedis

    adapter, presence, rate limits, nonces

  • QueueBullMQ finance jobs

    manual syncs, payout webhooks; retries

    • connects to Redis · queue state
    • connects to Revolut, Yapily · jobs

External

  • ExternalStripe

    payments, subscriptions

    • connects to Express API · signed webhooks
  • ExternalRevolut, Yapily

    bank data ingestion

Live path: browser to the Next.js BFF, then to the Express API over Railway private networking. Sockets authenticate with the same HttpOnly session cookie. Redis is the shared state that lets web replicas scale horizontally. The Caddy + Coraza WAF is built and deployable but not yet in the live path. Google OAuth, OpenAI (screening of activity posts), Cloudinary, Resend and Sentry are called from the API and omitted here for clarity.
Fig. 01.2

Payment correctness: one webhook, one effect

Row 1

  • ExternalStripe event

    retried until acknowledged

    • connects to Signature check
  • ServiceSignature check

    raw body, reject unsigned or altered

    • connects to Dedup insert
  • StoreDedup insert

    UNIQUE event id, ON CONFLICT DO NOTHING

    • connects to Side effects · new
    • connects to Already seen · duplicate
  • ServiceSide effects

    orders, subscriptions, intent upserts

    • connects to Journal posting
  • ServiceJournal posting

    double-entry lines, once per payment

Row 2

  • StageAlready seen

    no-op, acknowledged

Signature verification runs on the raw body. The event id is inserted under a UNIQUE constraint inside the same transaction as the side effects, so Stripe retries and concurrent replicas resolve to exactly one effect, including a single double-entry journal entry per successful payment.

04Interface

Eight dark, cinematic stills, one per Zone niche world, including a gaming headset, a motorcycle on a desert road, a barbell under a light shaft and a low car in a blue garage.
The eight niche worlds from the redesigned landing. Brand renders produced with AI image tools for the art direction; the product itself is code.

05Hard problems

  1. Making a stateful realtime monolith scale horizontally

    Problem

    Sockets, presence, rate-limit counters, scheduled jobs and database notifications all assumed a single process. A second replica would split users across sockets, duplicate events and run every cron job twice.

    What I did

    Redis became the shared state (Socket.IO adapter, presence, limiter stores). LISTEN/NOTIFY bridges emit only to local sockets, cron runs only where an environment gate allows it, and graceful shutdown drains HTTP, sockets, queues and pools for rolling deploys.

  2. Trusting the client IP through a proxy chain

    Problem

    Rate limits keyed on the left-most X-Forwarded-For entry could be bypassed, because that entry is supplied by the client.

    What I did

    I traced the real proxy chain through Railway's edge and the BFF, set an explicit trust-proxy depth and keyed the rate limiters on the derived client address instead of a raw header.

  3. Payment correctness under retries

    Problem

    Stripe retries webhooks, and with more than one replica the same event can arrive twice at the same moment.

    What I did

    The event id is recorded under a UNIQUE constraint inside the transaction that performs the side effects, and payment intents are upserted. Mode guardrails refuse live keys in development and test keys in production.

  4. Getting a real WAF to run on a PaaS

    Problem

    The Coraza plugin's convenience loader pulled in conflicting rule sets and failed on duplicate rule ids, and the build environment blocked git over HTTPS.

    What I did

    I pinned Caddy and the plugin to compatible versions, fetched the OWASP Core Rule Set as a tarball and wrote route-level exclusions so uploads and payments keep working.

06Decisions

A modular monolith, not microservices.
One deployable with strong module boundaries is cheaper to run and change for a solo engineer. Horizontal scaling comes from stateless web replicas plus Redis, not from splitting services.
Raw parameterised SQL, no ORM.
Explicit transactions and row locks where money moves, and schema changes that live in reviewed, numbered migrations.
A separate schema and identity for employees.
Staff access, RBAC and audit stay isolated from public accounts, and a compromised user session cannot reach internal tools.
BullMQ for queue-shaped work, in-process cron for the rest.
Manual syncs, payout webhooks and inbound bank transactions need retries and backoff, so they run as BullMQ jobs. Scheduled work stays in process behind an environment gate, because moving it did not justify a migration.
Rate limiters fail open when Redis is down.
A cache outage should mean requests are temporarily unlimited, never that every login is blocked.

07Status

Works today

  • Deployed on Railway, with the Next.js BFF fronting the API over private networking.
  • Messaging, calls, checkout, the employee portal and the backend commerce modules are implemented.
  • The redesigned interface wires cart, wishlist, orders and checkout; drops, gift cards and mystery boxes still show placeholder data there.
  • Security hardening and the first automated test suite (14 files) are on the develop branch.
  • The public site is mid-way through a documented design reset.

Not built yet

  • Moving the Caddy + Coraza WAF into the live edge path.
  • Enabling Stripe live mode behind a pre-launch checklist.
  • Separate staging and production environments.
  • Wiring subscription checkout, community group creation, drops, gift cards and mystery boxes into the new interface.
  • Reconciling the finance ledger code with the committed schema snapshot before live payments.

AI coding agents are part of the workflow here as well, working under repository rules (CLAUDE.md, AGENTS.md) that I maintain. Every commit is authored and integrated by me.

08What I learned

  • Scaling a stateful app is mostly about naming every piece of state and deciding where it lives.
  • Payment code should be written as if every message arrives twice.
  • Documenting what is not live yet keeps a solo project honest. Several plans in the repository were retired once the real topology was written down.

09Stack

  • Next.js
  • React
  • TypeScript
  • Tailwind CSS
  • GSAP
  • Node.js
  • Express
  • JavaScript
  • PostgreSQL
  • Redis
  • BullMQ
  • Socket.IO
  • WebRTC
  • Stripe
  • OpenAI API
  • Railway
  • Caddy + Coraza WAF
  • Docker
  • Sentry
  • GitHub Actions