nxt-backend
Purpose
nxt-backend is a NestJS / Nx backend for operating mini-grids: organizations and users, and (as they land) metering, payments, production monitoring, and related jobs.
Which of those feature areas actually run is config-selected. A JSON file turns capabilities and vendor integrations on or off; passwords and API keys stay in environment variables. Two deployments can therefore run different feature sets from the same tree without changing code.
What you can run today is still small: identity, organizations, and user administration, in two processes (api and worker). Metering, payments, notifications, field ops, and telemetry collectors are not loaded in those processes yet. Older service layouts and the full historical module set sit under legacy/ while that work continues.
Scope
- In scope:
- Two deployable processes:
api(HTTP) andworker(scheduled work). - Shared library
libs/core(@nxt/core). - Database schema in
supabase/migrations/(TypeScript types are generated from that schema and committed). - JSON config (
config.default.json, overridable) plus env secrets. When a metering feature is present and enabled in config, meter commands go over HTTP tonxt-device-messaging— this repo does not run that queue itself. legacy/— older apps and libraries kept as a reference. It is not what you deploy fromapps/.
- Two deployable processes:
- Out of scope on current
main:- Deploying the old four named services (
tiamat,talos,loch,yeti) as the product. Those trees live underlegacy/. - Complete metering, payments, notifications, or telemetry behavior until those modules are moved into
apps/api/apps/workerand turned on in config. - Operator dashboards and field apps (separate repositories that call
api).
- Deploying the old four named services (
Key components
What you run:
- Apps:
- Shared architecture:
Older layout (reference):
- Legacy stack — previous HTTP, provisioning, jobs, and telemetry services, plus diagrams drawn for that world.
Monorepo structure
| Path | Role |
|---|---|
apps/api | HTTP process. |
apps/worker | Scheduled / background process. |
libs/core | Config loader, Supabase client, logger, generated DB types. |
supabase/ | Migrations, local seed.sql, CLI config. |
config.default.json | Default JSON config ($schemaVersion: "1"). capabilities and integrations are empty objects until features land. |
legacy/ | Older Nx tree. Live typecheck does not include it. |
Nx 23, Node 24.x, pnpm 11. Projects you build: api, worker, core.
App boundary map
apihandles HTTP (CORS, request validation, JWT orX-API-KEY). It does not run cron.workerruns the Nest scheduler. Today it logsheartbeatevery 30 seconds and exposes no HTTP routes. It still bindsPORTso the process can start — that port is not a public API.libs/coreis shared plumbing and the config schema. Feature code (meters, payments, …) belongs in the process that serves it, behind a config flag, not dumped into core “just in case.”apiandworkerstart separately. They share the database, not a required HTTP mesh between them.nxt-device-messagingis a different repository. Wire it in only when metering exists in this codebase and is enabled in JSON. Until then,DEVICE_MESSAGING_*inapps/api/.env.exampledoes nothing.
Data architecture
- Now: Supabase Postgres holds organizations, accounts, members, API keys, and seeded grid rows. Supabase Auth issues JWTs. Prefer JWKS (
SUPABASE_JWKS_URL) over a shared HMAC secret.GET /healthonapireadsorganizationsto prove the database is reachable. - Later: TimescaleDB for high-frequency snapshots and collectors. That stack is not attached to
api/workeryet (older TypeORM code is underlegacy/libs/timeseries). - Schema changes are SQL migrations. Generated types in
libs/coremust be regenerated after migrations. You apply migrations to your own Supabase project on purpose (db push); a git push does not migrate production for you. Locally:pnpm exec supabase start/db reset.
Integration map
- Required today: Supabase (Auth + Postgres).
- Optional later, chosen in config: Victron, Solcast, CALIN, ChirpStack, Flutterwave, SendGrid, Africa's Talking, Telegram, Make, Jira, ZeroTier, EpiCollect, and similar. Missing optional vendors should skip that integration, not crash the process.
- Meter commands (when metering is on): HTTP + signed webhook to
nxt-device-messaging. STS token minting is that service’snxt-stsplugin, not a library insideapi. - Dashboards:
nxt-control-room,nxt-crm,nxt-field-ops, andnxt-topupare the HTTP clients this API is built for.
Runtime and deployment model
- Serve:
pnpm exec nx serve apiorworker. Check:pnpm exec nx run-many -t lint typecheck build test -p api,worker,core. - Config is read before the Nest app is created (
loadConfig()). Order:NXT_CONFIG_JSON(inline) →NXT_CONFIG_URL(reserved, not implemented) →NXT_CONFIG_PATH(file) → bundledconfig.default.json. Secrets:SUPABASE_URL,SUPABASE_SECRET_KEY, and so on. - Typical App Platform shape:
apiandworkerbuilt from this repo (Node.js buildpack). Addnxt-device-messagingas a third component only when metering is on. Hub: DigitalOcean App Platform. Source also hasdocs/deployment/supabase.md. - You can run a new
apibeside an old one and switch traffic. Do not run twoworkerprocesses that would execute the same jobs twice — stop the old worker, then start the new one.
How changes flow through the monorepo
- HTTP behavior →
api. Timed collectors and jobs →worker. - If the feature is optional, it should be gated in JSON so a deployment can leave it off. Off means the module is not constructed.
- Schema: add a file under
supabase/migrations/, thenpnpm generate-types:local. Do not hand-edit generated types. - Shared plumbing may go in
libs/core. Business rules for one feature stay with that feature. - Do not add product behavior in
legacy/. Move it intoapps/*(and config) instead. - Meter command HTTP/webhook shapes live in
nxt-device-messagingand@nxtgrid/device-messaging-contract, not inlegacy/device-message modules.
Setup and run
-
Repository: github.com/nxtgrid/nxt-backend
-
From the source
README.md:pnpm installpnpm exec supabase startpnpm exec supabase db reset # migrations + local seed.sqlpnpm generate-types:localpnpm exec nx serve api # or: worker -
Copy root
.env.example→.envandapps/api/.env.example→apps/api/.envaftersupabase start(publishable key + JWKS URL forapi). -
seed.sqlis for local development only (docs/deployment/supabase.mdin the source repo).
Source of truth
- Commands and current status:
README.md,AGENTS.md - Longer design notes:
docs/architecture/,docs/plans/002-oss-migration.md - Processes:
apps/api/src/modules/app.module.ts,apps/worker/src/modules/app.module.ts - Config:
libs/core/src/config/,config.default.json - Schema:
supabase/migrations/,supabase/seed.sql - Deploy:
docs/deployment/(hub: DigitalOcean App Platform) - Older tree:
legacy/