gold-app · engineering showcase

A real-time trading backend for gold coins, bullion and currency.

Solo-built Go backend for a phone-OTP fintech platform: live SSE price feed, Redis-backed quotes, WebSocket order updates and a double-entry ledger behind a Postgres transaction layer. This page walks through how it's put together — the sections below are a self-contained explanation for anyone reading my resume, no source access required.

spec sheet

runtime
Go 1.26 · Gin
storage
PostgreSQL 18 · Redis 9
realtime
WebSocket · Server-Sent Events
deploy
Docker · docker-compose · one VPS
auth
Phone OTP · Redis session store

Recorded walkthrough

A short screen recording of the platform in use — login, live prices, placing an order and the admin side of approving and settling it.

demo.mp4

Architecture

single Go binary, no microservices

Everything runs as one Gin application plus three background goroutines. Postgres is the source of truth for money and orders; Redis handles everything that's ephemeral or needs to fan out fast — sessions, quotes, rate limits and the price Pub/Sub feed. Nothing here needs a message queue.

Sources

  • External price hub API
  • SMS gateway (OTP delivery)
  • Admin console actions
  • User web app

Gin API + workers

  • Auth / OTP service
  • Market & quote service
  • Order & settlement service
  • Price feeder (poll → publish)
  • Order-expiry worker
  • Market daily-close worker

State & realtime

  • PostgreSQL — durable state
  • Redis — sessions, quotes, rate limits
  • Redis Pub/Sub → SSE price stream
  • WebSocket hub → per-user push

What it does, and how

12 entries

Kept as a ledger, since that's the theme of the system itself — every row is a piece of the backend, how it's implemented, and the problem it solves.

componentimplementationpurpose
OTP authenticationRedis rate limiter + 6-digit code, atomic verify via a Lua scriptPasswordless login for phone-based users, brute-force resistant
Session storeRedis hash per session, sliding TTL refreshed on every requestStateless API — any instance can serve any request
Live price feedPoll external price hub, publish to Redis Pub/Sub, stream over SSESub-second price updates without a persistent socket per client
Order notificationsWebSocket hub keyed by user ID, separate admin broadcast channelPush order status changes the instant an admin acts
Quote lockingSigned quote written to Redis with a short TTLFreezes a price for N seconds before an order can be placed
Order expiryRedis keyspace-notification listener + background workerUnconfirmed orders expire on their own — no polling loop
Market auto-closeTicker-based worker checking a configured close timeTrading stops on schedule without manual operator action
Settlement enginePartial money/asset settlement inside one DB transactionAn admin can settle an order in installments, safely
Double-entry ledgerAppend-only ledger + running balance, row-locked with FOR UPDATEFull audit trail, no negative-balance race conditions
Rate limiting & cooldownsRedis counters with TTL, scoped per phone number and per userStops OTP abuse and rapid-fire order placement
Role-based accessSession-derived claims checked by route middlewareOne session model powers both the user and admin panels
Safe migrationsgolang-migrate, with an automatic pg_dump before every deployRollback-ready schema changes in production

Life of an order

8 steps

From tapping "buy" to money and asset landing in the ledger — this is the exact path a trade takes through the system.

  1. 01

    Request an OTP

    The phone number is checked against the users and admins tables, Redis rate-limits repeat attempts, and an SMS provider sends the code.

  2. 02

    Verify and open a session

    The code is checked with an atomic get-and-delete Lua script. A session is written to Redis and a cookie is set on the response.

  3. 03

    Get a quote

    The client asks for a price. Buy and sell prices are derived from the live base price and the product's coefficients, then cached in Redis with a short TTL.

  4. 04

    Place an order

    The quote is atomically read and deleted from Redis, the price is sanity-checked against the current market rate, and a pending order is created with a matching expiry key.

  5. 05

    Admin review or auto-approval

    An admin approves or rejects the order — or the market's auto-approve setting does it instantly. Approval updates stock, balance and asset holdings in one transaction.

  6. 06

    Expiry, if nobody acts

    Redis fires a key-expired event; a background worker locks the order and marks it expired (or auto-approves it, depending on market settings).

  7. 07

    Settlement

    An admin settles the money and/or asset owed, in full or in parts. Every movement is written to an append-only ledger with a running balance.

  8. 08

    Reporting

    Exposure, profit, and top user/product reports are computed straight from the ledger and order tables for the admin dashboard.

Stack

Language & framework

  • Go 1.26
  • Gin
  • GORM

Data & realtime

  • PostgreSQL 18
  • Redis 9 — sessions, Pub/Sub, rate limiting
  • Server-Sent Events
  • WebSocket (gorilla/websocket)

Infra & ops

  • Docker multi-stage builds
  • docker-compose
  • golang-migrate
  • Automated pre-migration backups

This showcase site

  • TanStack Start
  • TanStack Router
  • React 19
  • Tailwind CSS v4
  • Docker

Data model

postgres · gorm · golang-migrate

Fourteen tables, grouped by what they're responsible for. Every money-moving table (orders, ledgers, settlements) is written inside a transaction with row-level locks — nothing here trusts the application layer to be the only writer.

Identity & access

  • admins
  • users
  • plans
  • plan_limits

Trading

  • products
  • markets
  • market_notes
  • orders
  • order_notes

Money & audit

  • assets
  • ledgers
  • asset_ledgers
  • settlements
  • audit_logs