Skip to content

Infrastructure

Describes the backend infra

Environments

Environment Branch Database Managed by
Local any Docker Compose Postgres Developer
Development dev Railway Postgres (auto-deploy) Railway
Staging stage Railway Postgres (auto-deploy) Railway
Production main Railway Postgres (auto-deploy) Railway

All Railway environments use the same railway.toml config. The only differences between them are environment variables set in the Railway dashboard (e.g. DATABASE_URL, NODE_ENV).


Local Development

Setup

cp .env.example .env           # DATABASE_URL already points at Docker Compose Postgres
docker compose up -d           # Start local Postgres
npx prisma migrate dev         # Apply all migrations + generate client
npm run start:dev              # Start NestJS with hot reload

What's running

  • Postgres 17 via Docker Compose on localhost:5432
  • NestJS natively on localhost:8080 (not containerized — avoids slow volume mounts on Windows, preserves hot reload)

Convenience scripts

Script What it does
npm run docker:up Start Postgres container
npm run docker:down Stop Postgres container
npm run docker:reset Destroy volume + restart (wipes all data)
npm run db:setup Start Postgres + run migrations

Database reset

To start fresh: npm run docker:reset && npx prisma migrate dev

This destroys the Docker volume (all data), recreates the container, and re-applies all migrations.


Prisma Workflow

Two commands, two purposes

Command When What it does Creates migration files? Safe for production?
prisma migrate dev Local development Diffs schema against DB, generates a new migration SQL file, applies it, runs prisma generate Yes No — interactive, may reset data
prisma migrate deploy CI / Railway Applies existing migration files in order, never creates new ones, never prompts No Yes — deterministic

Creating a migration (local only)

  1. Edit prisma/schema.prisma
  2. Run npx prisma migrate dev --name descriptive-name
  3. Prisma generates a SQL file in prisma/migrations/<timestamp>_<name>/migration.sql
  4. The migration is applied to your local DB
  5. prisma generate runs automatically, updating the client in src/generated/prisma/
  6. Commit the migration file — it's the source of truth for schema changes

Migration files are immutable

Once a migration SQL file is committed, never edit it. If a migration is wrong: - Create a new migration that fixes it - Or, if it's only on your local branch and hasn't been merged: npx prisma migrate reset to wipe and start over

Prisma config

prisma.config.ts defines schema location, migration path, and reads DATABASE_URL from .env. Both migrate dev and migrate deploy use this config.

schema.prisma has no url in the datasource block — it's provided at runtime by the config file (local) or by Railway's environment variable (deployed).


Railway Deployment Pipeline

Configured in railway.toml. All three environments (dev/staging/prod) follow the same pipeline:

What happens on git push

git push origin dev
┌─────────────────────────────────────────┐
│  1. BUILD                               │
│     Builder: Railpack                   │
│     - npm ci (automatic)                │
│     - npx prisma generate              │
│     - nest build                        │
│     Output: dist/ folder                │
└─────────────────┬───────────────────────┘
┌─────────────────────────────────────────┐
│  2. PRE-DEPLOY                          │
│     npx prisma migrate deploy           │
│     && npx prisma db seed (non-prod)    │
│     - Reads DATABASE_URL from Railway   │
│     - Applies pending migration files   │
│     - Seeds demo data (dev/staging only)│
│     - Prod runs migrate only (no seed)  │
│     - If this fails → deploy aborts,    │
│       old version keeps running         │
└─────────────────┬───────────────────────┘
┌─────────────────────────────────────────┐
│  3. START                               │
│     node dist/src/main.js               │
│     - Reads env vars from Railway       │
│     - Connects to Railway Postgres      │
└─────────────────┬───────────────────────┘
┌─────────────────────────────────────────┐
│  4. HEALTH CHECK                        │
│     GET /api/v1/health                  │
│     - Must return 200 within 300s       │
│     - Pings DB with SELECT 1            │
│     - If it fails → deploy rolls back   │
└─────────────────────────────────────────┘

Branch → Environment mapping

Set up in Railway dashboard (not in code):

  • Push to dev → deploys to development environment
  • Push to stage → deploys to staging environment
  • Push to main → deploys to production environment

Failure modes

Failure point What happens
Build fails (TS errors, missing deps) Deploy aborted, nothing changes
prisma migrate deploy fails Deploy aborted, old version keeps running, DB unchanged
Health check times out (300s) Deploy rolled back to previous version
App crashes after deploy Restarted up to 3 times (ON_FAILURE policy), then stops

Environment variables (Railway dashboard)

Each environment needs these set in Railway:

Variable Required Source / Default Notes
DATABASE_URL Yes Railway Postgres (${{Postgres.DATABASE_URL}}) Auto-managed by Railway
JWT_SECRET Yes Manual Token signing key — unique per environment
JWT_EXPIRATION Yes Manual Access token TTL (e.g. 15m)
NODE_ENV No Manual development / staging / production
PORT No Railway Usually auto-set by Railway
CORS_ORIGIN No Manual Comma-separated allowed origins
COOKIE_DOMAIN No Manual Cookie scope domain
LOG_LEVEL No Manual trace / debug / info / warn / error / fatal
REFRESH_TOKEN_EXPIRATION_DAYS No Default: 7 Refresh token TTL in days
SWAGGER_USER No Manual Basic Auth username for Swagger UI (dev/staging only)
SWAGGER_PASSWORD No Manual Basic Auth password for Swagger UI (dev/staging only)

Validated at boot time by src/config/env.validation.ts — missing required vars cause startup failure.


Typical Workflow: Schema Change

  1. Local: Edit schema.prisma, run npx prisma migrate dev --name add-users-table
  2. Commit: git add prisma/migrations/ prisma/schema.prisma
  3. Push to dev: Railway runs prisma migrate deploy → applies the new migration against dev DB
  4. PR to stage: Same migration runs against staging DB on merge
  5. PR to main: Same migration runs against production DB on merge

The migration SQL file is identical in every environment. The only difference is which DATABASE_URL it runs against.


railway.toml Reference

[build]
builder = "RAILPACK"
buildCommand = "npx prisma generate && npm run build"

[deploy]
startCommand = "node dist/src/main.js"
preDeployCommand = "npx prisma migrate deploy && npx prisma db seed"
healthcheckPath = "/api/v1/health"
healthcheckTimeout = 300
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3

[environments.prod.deploy]
preDeployCommand = "npx prisma migrate deploy"

This file is checked into the repo and applies to all Railway environments. The [environments.prod.deploy] override removes db seed from the pre-deploy command in production — only dev and staging get seeded. Environment-specific config (credentials, NODE_ENV) lives in the Railway dashboard, not here.


CI Pipeline

GitHub Actions CI (.github/workflows/ci.yml) runs on the stage branch only — triggered by PRs to stage and pushes to stage.

Pipeline steps

  1. npm ci — install dependencies
  2. npm audit — security audit (--audit-level=moderate)
  3. prisma generate — generate Prisma client
  4. prisma migrate diff — validate migration files match schema (catches drift)
  5. npm run lint — ESLint
  6. npm run build — TypeScript compilation
  7. npm test — unit tests (Jest)
  8. prisma migrate deploy — apply migrations to CI Postgres
  9. prisma db seed — seed test data
  10. npm run test:e2e — end-to-end tests (Supertest)

Branch strategy

Branch CI Deploy
dev None Railway auto-deploy to development
stage Full pipeline (audit → lint → build → unit → e2e) Railway auto-deploy to staging
main None Railway auto-deploy to production

The dev and main branches rely solely on Railway's deploy pipeline (build → pre-deploy → start → health check). Full CI validation gates the stage branch before code is promoted to main.