Skip to main content

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) and worker (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 to nxt-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 from apps/.
  • Out of scope on current main:
    • Deploying the old four named services (tiamat, talos, loch, yeti) as the product. Those trees live under legacy/.
    • Complete metering, payments, notifications, or telemetry behavior until those modules are moved into apps/api / apps/worker and turned on in config.
    • Operator dashboards and field apps (separate repositories that call api).

Key components​

What you run:

  • Apps:
    • api — HTTP: health, login identity, invite/update users.
    • worker — background process (today a scheduler heartbeat only).
  • Shared architecture:

Older layout (reference):

  • Legacy stack — previous HTTP, provisioning, jobs, and telemetry services, plus diagrams drawn for that world.

Monorepo structure​

PathRole
apps/apiHTTP process.
apps/workerScheduled / background process.
libs/coreConfig loader, Supabase client, logger, generated DB types.
supabase/Migrations, local seed.sql, CLI config.
config.default.jsonDefault 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​

  • api handles HTTP (CORS, request validation, JWT or X-API-KEY). It does not run cron.
  • worker runs the Nest scheduler. Today it logs heartbeat every 30 seconds and exposes no HTTP routes. It still binds PORT so the process can start — that port is not a public API.
  • libs/core is 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.”
  • api and worker start separately. They share the database, not a required HTTP mesh between them.
  • nxt-device-messaging is a different repository. Wire it in only when metering exists in this codebase and is enabled in JSON. Until then, DEVICE_MESSAGING_* in apps/api/.env.example does 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 /health on api reads organizations to prove the database is reachable.
  • Later: TimescaleDB for high-frequency snapshots and collectors. That stack is not attached to api / worker yet (older TypeORM code is under legacy/libs/timeseries).
  • Schema changes are SQL migrations. Generated types in libs/core must 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’s nxt-sts plugin, not a library inside api.
  • Dashboards: nxt-control-room, nxt-crm, nxt-field-ops, and nxt-topup are the HTTP clients this API is built for.

Runtime and deployment model​

  • Serve: pnpm exec nx serve api or worker. 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) → bundled config.default.json. Secrets: SUPABASE_URL, SUPABASE_SECRET_KEY, and so on.
  • Typical App Platform shape: api and worker built from this repo (Node.js buildpack). Add nxt-device-messaging as a third component only when metering is on. Hub: DigitalOcean App Platform. Source also has docs/deployment/supabase.md.
  • You can run a new api beside an old one and switch traffic. Do not run two worker processes that would execute the same jobs twice — stop the old worker, then start the new one.

How changes flow through the monorepo​

  1. HTTP behavior → api. Timed collectors and jobs → worker.
  2. 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.
  3. Schema: add a file under supabase/migrations/, then pnpm generate-types:local. Do not hand-edit generated types.
  4. Shared plumbing may go in libs/core. Business rules for one feature stay with that feature.
  5. Do not add product behavior in legacy/. Move it into apps/* (and config) instead.
  6. Meter command HTTP/webhook shapes live in nxt-device-messaging and @nxtgrid/device-messaging-contract, not in legacy/ device-message modules.

Setup and run​

  • Repository: github.com/nxtgrid/nxt-backend

  • From the source README.md:

    pnpm install
    pnpm exec supabase start
    pnpm exec supabase db reset # migrations + local seed.sql
    pnpm generate-types:local
    pnpm exec nx serve api # or: worker
  • Copy root .env.example → .env and apps/api/.env.example → apps/api/.env after supabase start (publishable key + JWKS URL for api).

  • seed.sql is for local development only (docs/deployment/supabase.md in 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/