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)¶
- Edit
prisma/schema.prisma - Run
npx prisma migrate dev --name descriptive-name - Prisma generates a SQL file in
prisma/migrations/<timestamp>_<name>/migration.sql - The migration is applied to your local DB
prisma generateruns automatically, updating the client insrc/generated/prisma/- 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¶
- Local: Edit
schema.prisma, runnpx prisma migrate dev --name add-users-table - Commit:
git add prisma/migrations/ prisma/schema.prisma - Push to
dev: Railway runsprisma migrate deploy→ applies the new migration against dev DB - PR to
stage: Same migration runs against staging DB on merge - 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¶
- npm ci — install dependencies
- npm audit — security audit (
--audit-level=moderate) - prisma generate — generate Prisma client
- prisma migrate diff — validate migration files match schema (catches drift)
- npm run lint — ESLint
- npm run build — TypeScript compilation
- npm test — unit tests (Jest)
- prisma migrate deploy — apply migrations to CI Postgres
- prisma db seed — seed test data
- 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.