API server
apps/server is the heart of Surplus. It handles every read and write, owns all business logic, and is the only component that touches the database, cache, and file storage. It's a Hono app running on Bun, with ORPC providing the typed API surface.
Entry point
The server starts from apps/server/src/index.ts, preloaded with apps/server/src/instrumentation.ts so OpenTelemetry is initialized before anything else:
# production
bun --preload ./src/instrumentation.ts ./src/index.ts
# local dev (hot reload)
bun run --hot --preload ./src/instrumentation.ts src/index.tsIt listens on port 8080 (or PORT).
What's in index.ts
index.ts assembles the Hono app and its middleware pipeline — Apitally logging, security headers, request context, rate limiting, CORS, and the ORPC handler — then starts the server with Bun.serve(). The exact order and reasoning are covered in Request lifecycle.
It also exposes two non-ORPC endpoints generated from the same contract:
/openapi.json— the OpenAPI 3 spec, generated from the ORPC router./openapi— an interactive Scalar API reference UI.
Layout
apps/server/src/
index.ts — app assembly + server start
instrumentation.ts — OpenTelemetry preload
routes/ — one file per endpoint, mirroring the contract
hubs/list.route.ts
auth/otp/request.route.ts
files/serve.route.ts
...
helpers/
orpc.ts — assembles all route handlers into the router
middleware.ts — authMiddleware, error mapping
services.ts — dependency injection: builds service instances
env.ts — Zod-validated runtime configuration
rateLimit.ts — Redis-backed rate limitersThe routes/ tree mirrors the contract: each contract entry has a matching *.route.ts handler, and helpers/orpc.ts wires them all into a single router.
Dependencies it injects
helpers/services.ts is where the real world gets wired in. It constructs the database clients (primary and read replica), the Redis client, the S3 client, and the Stripe and Resend integrations, then injects them into the service classes from @surplus/services. Services themselves never read environment variables — they receive everything they need.
Configuration
All runtime configuration is validated up front in apps/server/src/helpers/env.ts. The server Dockerfile runs an env-check at build time so a misconfigured deploy fails fast rather than at runtime. Required values include the database connection strings, Redis credentials, S3 keys, the JWT signing secret, and Stripe/Resend keys.
Deployment
The server builds via apps/server/Dockerfile and runs on Railway (three replicas in production). See Build and deploy and, for the security posture of these dependencies, Infrastructure and secrets.