Skip to main content

Service

packages/service is the core of Routerly. It is a Fastify HTTP server that handles authentication, intelligent model routing, provider dispatch, cost accounting, and budget enforcement.

The CLI and the dashboard both communicate with the running service over HTTP — there is no other inter-process communication.


Startup Sequence​

When the service starts it performs the following steps in order:

  1. initConfigDirs — Creates ~/.routerly/config/ and ~/.routerly/data/ if they do not exist.
  2. loadSecret — Reads the AES-256 encryption key from ~/.routerly/config/secret, generating and persisting it automatically if missing. API keys are encrypted with this secret at rest.
  3. readConfig('settings') — Loads settings.json. Missing file → defaults are written and used.
  4. buildServer — Registers Fastify plugins, routes, and middleware in this order:
    • CORS (@fastify/cors)
    • Dashboard static files (@fastify/static at /dashboard, only if dashboardEnabled: true)
    • Management API routes (/api/*)
    • Auth guard plugin (validates Bearer sk-rt-* tokens for /v1/* routes)
    • LLM Proxy routes (/v1/*)
    • Root redirect (/ → /dashboard/)
    • Health check (/health)
  5. server.listen — Binds to host:port from settings (defaults: 0.0.0.0:3000).

Running Manually​

The service is normally managed as a background daemon by the installer. You can also start it directly:

# From the monorepo root (development)
npm run dev --workspace packages/service

# Standalone binary (after installation)
~/.routerly/app/routerly-service

# Specify a custom config/data directory
ROUTERLY_HOME=/opt/routerly ~/.routerly/app/routerly-service

Environment variables override settings.json — see Environment Variables for the full list.


Health Check​

The service exposes a lightweight health endpoint at GET /health:

curl http://localhost:3000/health
{
"status": "ok",
"version": "0.1.5",
"timestamp": "2026-03-27T12:00:00.000Z"
}

Returns HTTP 200 when the process is up and accepting connections. This endpoint does not require authentication and is suitable for load balancer and container health probes.


Configuration Storage​

All state is stored as JSON files on disk — there is no external database. The base directory defaults to ~/.routerly/ and can be overridden with $ROUTERLY_HOME.

~/.routerly/
├── config/
│ ├── settings.json # Port, log level, dashboard toggle, timeout, public URL
│ ├── models.json # Registered LLM models (API keys AES-256 encrypted)
│ ├── projects.json # Projects, routing policies, tokens, members, budgets
│ ├── users.json # Dashboard users (passwords bcrypt-hashed)
│ ├── roles.json # Custom RBAC role definitions
│ └── secret # AES-256 encryption key (auto-generated, never commit)
└── data/
└── usage.json # Append-only call records (tokens, cost, latency, outcome)

All writes to config files use a file lock (proper-lockfile) to prevent concurrent corruption. Missing files are auto-created with their defaults on first read.


Process Signals​

SignalBehaviour
SIGTERMGraceful shutdown — Fastify drains in-flight requests before exiting
SIGINTSame as SIGTERM (Ctrl-C in a terminal)
SIGHUPNot specially handled — restart the process to reload config

Config changes made via the CLI or dashboard API take effect immediately (the service re-reads files on each request) without a restart, except for port and host which require a restart.


Logging​

The service uses pino via Fastify's built-in logger.

NODE_ENVFormatDefault level
developmentpretty-printed with colours (pino-pretty)info
productionJSON (one object per line)info

Change the log level in settings.json, via the dashboard (Settings → General → Log Level), or with the ROUTERLY_LOG_LEVEL environment variable.