A high-performance trading simulation platform built as a exness monorepo. It streams live market data, executes orders through a lightweight engine layer, and renders a responsive trading UI.
- apps/web (Next.js 14)
- App Router UI for trading:
Assets,PriceChart,TradingPanel,MobileTradingDrawer,UserOrders - Cookie-based auth (JWT in httpOnly cookie
token) and route protection viamiddleware.tsfor/trade - Live prices via a shared
price-storefed by a single websocket connection - React Query for API mutations/queries, Tailwind + shadcn/ui for styling
- App Router UI for trading:
- apps/api (Express)
- REST API (auth, trading)
- Magic-link sign-in:
POST /api/v1/auth/signupissues a verification link;GET /api/v1/auth/signin/verify?token=...setstokenhttpOnly cookie and redirects to the web app - JWT validation middleware reads the
tokencookie (authMiddleware.ts) - Prisma ORM to Postgres
- apps/ws (Node ws)
- Subscribes to Redis pub/sub channel
ws:price:update - Forwards aggregated price updates to connected browser clients
- Subscribes to Redis pub/sub channel
- apps/poller (Data ingestion)
- Connects to Backpack Exchange websocket (
wss://ws.backpack.exchange/) - Aggregates and publishes price snapshots every ~100ms to Redis channel
ws:price:update; also pushes to Redis Streams (stream:engine)
- Connects to Backpack Exchange websocket (
- apps/engine
- Handlers that react to Redis streams and manage in-memory state
- Communicates with Redis, DB (and Mongo in docker compose for auxiliary storage)
- packages/db (Prisma)
- Prisma schema + migrations; exposes
@imex/dbclient
- Prisma schema + migrations; exposes
- packages/redis
- Lightweight Redis client, pub/sub and streams utils used by
wsandpoller
- Lightweight Redis client, pub/sub and streams utils used by
- packages/eslint-config, packages/typescript-config
- Shared lint and TS configs
- Poller subscribes to exchange, aggregates ticks → publishes
{ type: 'PRICE_UPDATE', data: JSON.stringify(priceMap) }to Redis every ~100ms. - WebSocket service (apps/ws) receives pub/sub messages and broadcasts them to all connected clients.
- Web client (apps/web) maintains a single shared WS connection (
useWebSocket) and writes to a sharedprice-store(useSymbolPrice). - UI components render bid/ask and compute order sizing using the store.
- Orders are posted to the API; engine and DB update state; user orders and balances are re-fetched in the UI.
- Sign-in flow: User submits email → API generates a short-lived JWT link → user opens link → API verifies and sets
tokencookie (httpOnly, SameSite=Lax) → redirects to/trade. - Protection:
apps/web/src/middleware.tsredirects unauthenticated users from/tradeto/login. - API protection:
authMiddleware.tsverifiestokencookie and setsreq.user.
- Web: Next.js 14 (App Router), React 18, Tailwind CSS, shadcn/ui, TanStack Query, Axios, Lucide icons
- API: Express, Zod (validation), JWT, Prisma, PostgreSQL
- Realtime: ws (WebSocket), Redis (pub/sub, streams)
- Engine: Node TypeScript, Redis, in-memory state
- Tooling: Turborepo, Bun (package manager/runtime), TypeScript, ESLint/Prettier, Docker Compose
- apps/
- api/ (Express REST)
- engine/ (engine handlers)
- poller/ (market data ingestion)
- web/ (Next.js 14 app)
- ws/ (WebSocket broadcaster)
- packages/
- db/ (Prisma schema/client)
- redis/ (Redis helper lib)
- eslint-config/, typescript-config/
- docker/ (Dockerfiles for api, engine, poller, ws)
- docker-compose.yml (local infra and app containers)
Prerequisites:
- Bun 1.1.x (declared in
packageManager) - Node.js >= 18
- Postgres 15 running locally (or a DATABASE_URL you can reach)
- Redis 7 running locally
- Install deps
bun install- Configure env Create the following env files or export these in your shell. Example minimal vars:
- API (apps/api)
- JWT_SECRET=your_jwt_secret
- API_BASE_URL=http://localhost:4000/api/v1
- FRONTEND_URL=http://localhost:3000
- DATABASE_URL=postgresql://USER:PASSWORD@localhost:5432/DBNAME
- Web (apps/web)
- NEXT_PUBLIC_API_BASE_URL=http://localhost:4000/api/v1
- NEXT_PUBLIC_WS_URL=ws://localhost:8080
- WS (apps/ws)
- WS_PORT=8080
- Redis connection envs if your redis package expects them
- Poller (apps/poller)
- Redis connection envs if your redis package expects them
- Engine (apps/engine)
- DATABASE_URL (if required by the engine)
- Redis connection envs
- Prepare database
bun run db:generate
bun run db:migrate- Run services (each in its own terminal)
bun run start:ws # WebSocket broadcaster on :8080
bun run start:api # REST API on :4000
bun run start:poller # Market data ingestor
bun run start:engine # Engine- Run the web app
cd apps/web
bun install
bun run dev # http://localhost:3000Sign-in: open http://localhost:3000/login, request magic link (in dev the API logs the verify URL), open the verify URL to set the cookie and get redirected to /trade.
Docker Compose will start Postgres, Redis, Mongo, and app services.
- Create env files referenced in compose:
- env.dockercompose/.env.backend (for api)
- env.dockercompose/.env.web-socket (for ws)
- env.dockercompose/.env.engine (for engine)
- env.dockercompose/.env.poller (for poller)
Ensure these contain the variables described in the local setup (JWT_SECRET, DATABASE_URL, etc.).
- Build and start
docker compose up --buildServices/ports:
- api: 4000
- ws: 8080
- db (postgres): 5432
- redis: 6379
- mongo: 27017
Note: The Next.js web app is not included in compose; run it locally (see local web step).
- bun run dev # turbo dev across workspace
- bun run build # turbo build across workspace
- bun run lint # turbo lint across workspace
- bun run db:generate # prisma generate
- bun run db:migrate # prisma migrate
- bun run start:api # start API
- bun run start:ws # start WS
- bun run start:poller # start Poller
- bun run start:engine # start Engine
- Auth cookie name:
token(httpOnly, SameSite=Lax) - Protected route:
/trade(web enforced byapps/web/src/middleware.ts) - WS price channel: Redis pub/sub
ws:price:update - Web price store:
apps/web/src/lib/price-store.tswithuseSymbolPrice(symbol)
- 401s in the web app: cookie may be missing/expired; sign in again.
- No prices in UI: verify poller is running, ws is broadcasting on :8080, and
NEXT_PUBLIC_WS_URLmatches. - DB errors: confirm
DATABASE_URLand that migrations have been run.
MIT (or your preferred license)