Files
airi/server/README.md
T

5.8 KiB

AIRI Backend

Project AIRI's hosted backend source lives under this folder. Workspace package names stay stable; the directory groups service source and database ownership while production deployment configuration remains in proj-airi/airi-railway.

Layout

  • apps/api: resource API, business domains, database migrations, and API runtime.
  • apps/auth: standalone Better Auth and OIDC service.
  • packages/auth-shared: Auth-owned database schema and principal contracts.
  • packages/server-sdk-shared: Eventa contracts for the hosted chat WebSocket.
  • dev/caddy: local-only public edge routing for the shared Auth/API origin.
  • docker-compose.yaml: complete local API + Auth + PostgreSQL + Redis + Caddy stack.

Run locally

From the repository root:

pnpm dev:backend

The command uses server/docker-compose.yaml and exposes only Caddy at http://localhost:6112.

Railway deployment

API and Auth are separate long-running Railway services built from the same repository. Keep each service's Root Directory at the repository root. Both Dockerfiles copy workspace manifests and shared packages from that build context. The project-level Railway configuration lives in proj-airi/airi-railway/.railway/railway.ts.

Service Public role Private dependency
Resource API Product and resource API Auth issuer and JWKS
Auth Better Auth and OIDC issuer Resource API deletion endpoint

The Infrastructure as Code file pins each Dockerfile, start command, /readyz healthcheck, and watch patterns. Run the Railway plan from the deployment repository before you apply a change.

Service-to-service contract

Share database, Redis, and observability variables using Railway reference variables rather than copying secret values between services. Configure the two directional private links as follows:

Consumer Variable Value source Purpose
Resource API AUTH_SERVER_URL Auth's canonical public issuer URL JWT issuer, audience, and public JWKS identity
Resource API AUTH_SERVER_INTERNAL_URL Auth's Railway private domain Private JWKS fetch; it does not change issuer validation
Auth PUBLIC_URL Auth's canonical public issuer URL Better Auth and OIDC issuer URL; must equal API AUTH_SERVER_URL
Auth RESOURCE_SERVER_URL API's Railway private domain Private call before deleting a user's business data

Set RATE_LIMIT_TRUSTED_PROXY=railway only for services directly receiving Railway proxy traffic. Keep /internal/* private: Auth calls the API over its private domain, and public routing must not expose the API's internal Auth routes.

The API remains the shared database migration owner. Do not add a Railway pre-deploy migration command to Auth, and do not make Auth startup run shared migrations. After either service deploys, Railway must receive 200 from that service's /readyz; deployment success alone is not sufficient evidence that the service can reach its required dependencies.

Dev environment

The Railway dev environment is one shared slot. It has fixed public URLs, so OAuth callbacks and payment webhooks stay registered when the code changes.

Work on your own branch in this repository. Do not merge into a shared test branch. The server-dev branch is no longer a deploy source.

To test a branch, run the Deploy to server-dev workflow:

gh workflow run deploy-server-dev.yml -f branch=<branch> -f reset_database=true

The workflow does these steps in order:

  1. It resets the dev Neon branch to its parent branch, production. The dev database then holds a copy of the production schema and data.
  2. It sets the source branch of the Railway ts-api and auth-server services and deploys them. The API applies its migrations on startup.
  3. It starts the Cloudflare Workers (server-dev) workflow on the same branch.

Railway deploys each later push to the branch. The frontend does not follow pushes. Run the workflow again with reset_database=false to build it again.

The branch must be in this repository. Railway cannot deploy a branch of a fork.

The reset removes all data that the previous branch wrote. This includes dev accounts that are not in production. Set reset_database=false only when the database already has the migrations of the branch and no others. The Drizzle migrator applies only migrations that are newer than the last applied migration. If the database already has a newer migration from a different branch, the migrator skips the older migrations of the new branch and reports no error.

The reset does not clear Redis. Cached values from the previous branch can stay until they expire.

The workflow needs this repository configuration:

Name Kind Value
RAILWAY_DEV_TOKEN Secret Railway project token for the dev environment
NEON_API_KEY Secret Neon API key for the project that holds the dev database
NEON_PROJECT_ID Variable Neon project ID
NEON_SERVER_DEV_BRANCH Variable Name of the Neon branch that the dev DATABASE_URL points to

The dev Neon branch is serverlize-dev. Its parent is production, so the Neon API key can also read the production branch. Keep the key in the secret only.

Package boundaries

Frontend applications remain under apps/. Hosted-backend packages that define a resource API protocol can live under server/packages/, even when a frontend consumes their generated contract.

Cross-runtime server SDK and protocol packages remain under packages/ because Web, Electron, plugins, and independent services consume them.

Production Caddy routing, OpenTelemetry Collector configuration, observability storage, and Grafana dashboards live in proj-airi/airi-railway so deployment topology is not duplicated in the application repository.