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.
Architecture
single Go binary, no microservicesEverything 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 entriesKept 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.
| component | implementation | purpose |
|---|---|---|
| OTP authentication | Redis rate limiter + 6-digit code, atomic verify via a Lua script | Passwordless login for phone-based users, brute-force resistant |
| Session store | Redis hash per session, sliding TTL refreshed on every request | Stateless API — any instance can serve any request |
| Live price feed | Poll external price hub, publish to Redis Pub/Sub, stream over SSE | Sub-second price updates without a persistent socket per client |
| Order notifications | WebSocket hub keyed by user ID, separate admin broadcast channel | Push order status changes the instant an admin acts |
| Quote locking | Signed quote written to Redis with a short TTL | Freezes a price for N seconds before an order can be placed |
| Order expiry | Redis keyspace-notification listener + background worker | Unconfirmed orders expire on their own — no polling loop |
| Market auto-close | Ticker-based worker checking a configured close time | Trading stops on schedule without manual operator action |
| Settlement engine | Partial money/asset settlement inside one DB transaction | An admin can settle an order in installments, safely |
| Double-entry ledger | Append-only ledger + running balance, row-locked with FOR UPDATE | Full audit trail, no negative-balance race conditions |
| Rate limiting & cooldowns | Redis counters with TTL, scoped per phone number and per user | Stops OTP abuse and rapid-fire order placement |
| Role-based access | Session-derived claims checked by route middleware | One session model powers both the user and admin panels |
| Safe migrations | golang-migrate, with an automatic pg_dump before every deploy | Rollback-ready schema changes in production |
Life of an order
8 stepsFrom tapping "buy" to money and asset landing in the ledger — this is the exact path a trade takes through the system.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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).
- 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.
- 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-migrateFourteen 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