Skip to main content

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 to dist/index.js
  • Interactive API reference: /api-reference (generated from this app's OpenAPI spec)

Tech stack

ConcernChoice
HTTPExpress 5
ORM / DBPrisma 7 + PostgreSQL 16
AuthJWT (jsonwebtoken), bcrypt, SHA-256 token hashes
API docs@asteasolutions/zod-to-openapi + openapi-comment-parser, served via Swagger UI at /docs
Artifact storageCloudflare 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):

VariablePurpose
PORTHTTP port (default 3000)
CORS_ORIGINComma-separated allowed origins
DATABASE_URLPostgres connection string
JWT_SECRETSecret for signing JWTs
ADMIN_API_KEYStatic key for the admin API surface
ADMIN_EMAIL / ADMIN_PASSWORD / ADMIN_NAMESeed admin account
R2_ACCOUNT_ID / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY / R2_BUCKETCloudflare 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.