API server (apps/api)
The cloud control plane. An Express 5 service backed by Postgres that manages customers, devices, modules, dashboard versions, and OTA (over-the-air) updates, and exposes the publish registry used by CI to release modules/customers.
- Workspace:
apps/api· package name:server - Entry point:
src/index.ts→ built todist/index.js - Interactive API reference: /api-reference (generated from this app's OpenAPI spec)
Tech stack
| Concern | Choice |
|---|---|
| HTTP | Express 5 |
| ORM / DB | Prisma 7 + PostgreSQL 16 |
| Auth | JWT (jsonwebtoken), bcrypt, SHA-256 token hashes |
| API docs | @asteasolutions/zod-to-openapi + openapi-comment-parser, served via Swagger UI at /docs |
| Artifact storage | Cloudflare R2 (S3-compatible) via @signapps/artifact-storage |
| Release compose | @signapps/ha-tooling |
Usage
Run in dev
# 1. start Postgres (docker-compose in the app dir)
pnpm --filter server run db:up
# 2. run the API in watch mode (root script)
pnpm dev:api
pnpm dev:api expands to:
prisma generate && prisma migrate deploy \
&& pnpm --filter @signapps/ha-tooling... run build \
&& tsx watch src/index.ts
So it generates the Prisma client, applies migrations, builds the ha-tooling
dependency, then runs with hot reload. The server listens on PORT (default
3000 in dev; 3001/3010 conventions in production — see
ecosystem.config.cjs).
Useful sub-commands:
pnpm --filter server run db:up # start Postgres
pnpm --filter server run db:migrate # prisma migrate dev
pnpm --filter server run admin:seed # seed the admin account
pnpm --filter server run docs:openapi # regenerate the OpenAPI spec
pnpm --filter server run publisher:mint-token # mint a CI publisher token
Build
pnpm --filter server run build
Runs prisma generate, builds @signapps/ha-tooling and @signapps/types, then
tsc. Output is dist/, started with node dist/index.js.
Deploy
The API is run under PM2 in production (ecosystem.config.cjs) and deployed
to a server over SSH (the repo's deploy tooling uses ssh/scp/rsync, not a
managed platform). CI release workflows publish artifacts to R2 and call this
API's publish endpoints — see Releases & deploys.
Configuration
Environment variables (see apps/api/env.example):
| Variable | Purpose |
|---|---|
PORT | HTTP port (default 3000) |
CORS_ORIGIN | Comma-separated allowed origins |
DATABASE_URL | Postgres connection string |
JWT_SECRET | Secret for signing JWTs |
ADMIN_API_KEY | Static key for the admin API surface |
ADMIN_EMAIL / ADMIN_PASSWORD / ADMIN_NAME | Seed admin account |
R2_ACCOUNT_ID / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_BUCKET | Cloudflare R2 artifact storage |
Structure
src/index.ts wires CORS, JSON parsing, morgan logging, the Swagger UI at
/docs (+ /docs.json), a /health check, the /api router, and a background
job that prunes old artifacts every 24h. Routes are mounted under /api:
/api/users user management
/api/customers customer data + version publish
/api/devices device management + check-in
/api/modules module registry + version publish
/api/dashboard-versions dashboard releases (alias /api/dashboards)
/api/channels release channels
/api/admin admin endpoints (static API key)
/api/scaffolding project scaffolding
/api/agent-releases edge-agent OTA updates
How OpenAPI is generated
This app is the source of the spec that powers both the in-app Swagger UI and this site's API reference.
Paths come from JSDoc above each controller handler (parsed by
openapi-comment-parser); request/response shapes come from the Zod schemas in
@signapps/types. Run pnpm --filter server run docs:openapi to regenerate.
Authentication
Three auth mechanisms (src/middleware/auth.ts), all comparing SHA-256 hashes:
Publisher endpoints (used by CI to publish module/customer versions) use a
separate scoped-token middleware (requirePublisherScope) — see
Releases & deploys.