Conduit API — Architectural Constitution
Identity
RealWorld Conduit API. TypeScript · Express 4 · Prisma 5 · PostgreSQL. Deployed on Railway.
Architecture
Hexagonal (Ports & Adapters). Strict layering: Routes → Services → Repositories → Prisma.
Never skip layers. Routes call services. Services call repositories. Repositories call Prisma.
Error Contract
All errors extend AppError with toJSON() returning { errors: { <key>: [message] } }:
| Error class | HTTP | Key |
|---|---|---|
ValidationError | 422 | field name (Zod path) |
ConflictError | 409 | field name (e.g. username) |
AuthenticationError | 401 | token (missing) or credentials (invalid login) |
AuthorizationError | 403 | resource name (e.g. article, comment) |
NotFoundError | 404 | resource.toLowerCase() |
Validation
Zod schemas in src/validation/. All messages follow RealWorld convention: "can't be blank", "has already been taken", "is invalid". Use zodFieldErrors(error) to extract field-keyed errors.
Null Semantics
bio and image fields return null (never ""). Use value || null not value ?? null — empty string must coerce to null.
Rate Limiting
Production only (NODE_ENV=production). Configurable via RATE_LIMIT_WINDOW_MS and RATE_LIMIT_MAX_REQUESTS env vars. Default: 500 req/min.
Deployment
railway up --service conduit-api --detach
Migrations run automatically on container start via npx prisma migrate deploy. Prisma binary target: linux-musl-openssl-3.0.x for Alpine.
Probe Loop
Run forgecraft run_harness (L2) and k6 probes (L4) against Railway URL. All 13 behavioral probes and 3 SLO probes must pass before close_cycle.