API gateway, routing engine, and pricing service for StableRoute — Stellar liquidity routing.
- Express REST API (TypeScript)
- Health and quote endpoints as a base for the routing engine and pricing service
See docs/api.md for the complete endpoint and error-code
reference, including request/response shapes and curl examples.
- Node.js 18+
- npm
- Clone the repo and enter the directory:
git clone <repo-url> && cd stableroute-backend
- Install dependencies:
npm install
- Build and test:
npm run build npm test - Run locally:
API:
npm run dev
http://localhost:3001(orPORTenv var). See Configuration for the full list of environment variables and how to use the.env.exampletemplate.
The backend is configured entirely through environment variables. The table below lists every variable the code reads — there are no others.
| Variable | Purpose | Default | Example |
|---|---|---|---|
PORT |
TCP port the HTTP server binds to. | 3001 |
8080 |
NODE_ENV |
Runtime mode. Setting it to test disables the rate limiter and per-request logging (used by Jest). |
(unset) | production |
.env.example is the template for these variables. Copy it to .env
and edit the values for local development:
cp .env.example .env.env is git-ignored (see .gitignore), so your local values are never
committed. The application does not auto-load .env; export the
variables into your shell (or use your process manager / --env-file)
before starting the server.
| Script | Description |
|---|---|
npm run build |
Compile TypeScript to dist/ |
npm run start |
Run production server (dist/index.js) |
npm run dev |
Run with ts-node-dev (watch) |
npm test |
Run Jest tests |
npm run lint |
Run ESLint |
On every push/PR to main, GitHub Actions runs:
npm cinpm run buildnpm test
Ensure these pass locally before pushing.
GET /api/v1/health/deep is designed as a Kubernetes readiness probe. It reports:
status:"ok"if all checks pass and the service is not paused;"paused"if the admin pause has been toggled;"degraded"if any required health check fails.checks[]: An array of{ name, status, durationMs }objects, one per dependency. Current checks:storage— verifies the in-memory store can write and read back.clock— verifies the system clock is producing post-epoch timestamps.
uptimeSeconds,memory(rssMb, heapUsedMb),pid,node— kept for backward compatibility.
When any required check fails, the endpoint returns 503 with
status: "degraded". When the service is paused it returns 200 with
status: "paused". When all checks pass it returns 200 with
status: "ok".
Checks are time-bounded (5s timeout via AbortController) so the probe
never hangs.
The OpenAPI document is the single source of truth in src/openapi.ts
(exported as openApiSpec). The GET /api/v1/openapi.json handler serves it
verbatim instead of an inline literal, so the spec can be imported by tests.
src/__tests__/openapi.test.ts includes a route-drift guard that walks the
Express router stack, converts each registered /api/v1/... route to its
OpenAPI templated form (:param → {param}), and asserts every discovered path
appears as a key in openApiSpec.paths. This makes it impossible to ship a new
endpoint without documenting it.
Handlers use a shared sendError helper so 400/404/413/500-style responses keep the canonical { error, message, requestId } shape. The request id is attached before JSON parsing, which keeps body-parser errors correlated with the X-Request-Id response header.
See CONTRIBUTING.md for the full workflow, branch naming, local checks, and PR expectations.
Quick checklist:
- Fork the repo and create a branch from
main. - Install deps, add tests for new behavior, keep
npm run build,npm run lint, andnpm testpassing. - Open a PR; CI must be green.
Test coverage thresholds are enforced in CI via Jest's coverageThreshold.
Current targets: statements ≥ 90%, branches ≥ 80%, functions ≥ 88%,
lines ≥ 90%.
Note:
server.tsis now refactored into side-effect-free, exported functions (createServer,registerSignalHandlers,start) with the actualapp.listenguarded byrequire.main === module. It can therefore be imported and exercised bysrc/__tests__/server.test.ts(it starts on an ephemeral port, serves/health, and closes cleanly) without keeping the event loop alive. The signal-handler shutdown body callsprocess.exit, so it is deliberately not invoked under test, which is whyserver.tskeeps a small amount of uncovered branch.
Generate a local coverage report:
npm run test:coverageCoverage reports are uploaded as a CI artifact on every push/PR.
MIT