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.

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.
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
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
04Interface

05Hard problems
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.
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.
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.
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