- loadConfig: pure over env object, accumulates all problems, names var names only - DATABASE_URL now required at startup; PORT/HOST/LOG_LEVEL/NODE_ENV/REDIS_URL defaulted - flags module behind FeatureFlagProvider; unknown flags OFF; runtime setEnabled (no redeploy) - buildApp decorates app.flags; server.ts fail-fast before app boot - tests caught and fixed flag-store case-normalization bug before gates - zero new dependencies; all gates approved; verify.sh green
MercadoDeVida backend — modular monolith skeleton
TypeScript + Fastify modular monolith. Simple code, clear modules, small changes, no magic.
Requirements
- Node.js >= 22
- npm
Commands
npm install # install dependencies
npm run build # compile to dist/
npm start # run compiled server (PORT, HOST env vars)
npm test # vitest unit tests (no database needed)
npm run typecheck # tsc --noEmit
npm run lint # eslint + prettier check
npm run lint:boundaries # module boundary check
Configuration
Startup is fail-fast: src/infrastructure/config parses env once and refuses to boot
on missing/invalid required vars. DATABASE_URL is required; PORT, HOST,
LOG_LEVEL, NODE_ENV, REDIS_URL are optional with defaults. Errors name variable
NAMES only, never values.
Feature flags: FLAG_<NAME>=true|false env vars seed the flag store at boot. Unknown
flags default to OFF (fail-safe). Flags flip at runtime through the store — activation
is separate from deployment (no redeploy). Copy .env.example to .env to start.
HTTP contract
- Every response carries an
x-request-idheader (propagated from a safe incomingx-request-id, or a fresh UUID). Every JSON log line for a request carries the same id. - Errors always use one envelope:
{ "error": { "statusCode", "code", "message", "details?" }, "requestId" }Codes:NOT_FOUND,VALIDATION_ERROR,BAD_REQUEST/Fastify 4xx codes,INTERNAL_ERROR. 5xx messages are always generic; stack traces stay in server logs only. - Input validation is explicit per route:
parseJson(schema, body)(zod) in the handler. - Log level via
LOG_LEVELenv var (defaultinfo); logs are JSON only.
Database (local dev)
cp .env.example .env # once
npm run docker:up # start PostgreSQL 16 + Redis 7
npm run db:up # apply migrations
npm run db:status # list applied migrations
npm run db:down # revert last migration
npm run test:integration # integration tests (need TEST_DATABASE_URL from .env)
npm run docker:down # stop services (add -v to wipe volumes)
Table naming convention
<module>_<table> e.g. catalog_products, inventory_stock, orders_orders
- Every table is prefixed with its owning module.
- A module never queries tables without its own prefix; data flows through module interfaces.
- Migrations are immutable once merged: fixes ship as new migrations.
- No schema change without migration.
Layout
src/
├── app/ # composition root (only place that wires modules)
├── infrastructure/ # http server entrypoint (later: db, redis, providers)
├── modules/ # business modules, one folder each
│ └── health/ # exemplar module: public API only via index.ts
└── shared/ # cross-cutting helpers (error envelope)
Module rules
- A module exposes its public API only through its
index.ts. - Files inside a module may import: own subtree,
src/shared, Node builtins, npm packages. - Code outside modules (app/infrastructure) may import a module only via its
index.ts. npm run lint:boundariesenforces these rules.