- request_id generated or sanitized-propagated on every request (x-request-id)
- structured JSON logging (pino), one correlated line per request, injectable logger
- error envelope v2 { error: { statusCode, code, message, details? }, requestId }
- 5xx messages always generic; stack traces stay in server logs only
- explicit parseJson (zod) input validation hook at the API layer
- README HTTP contract section; deps justified in spec/tech.md
- all gates approved; verify.sh green
73 lines
2.6 KiB
Markdown
73 lines
2.6 KiB
Markdown
# MercadoDeVida backend — modular monolith skeleton
|
|
|
|
TypeScript + Fastify modular monolith. Simple code, clear modules, small changes, no magic.
|
|
|
|
## Requirements
|
|
|
|
- Node.js >= 22
|
|
- npm
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
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
|
|
```
|
|
|
|
## HTTP contract
|
|
|
|
- Every response carries an `x-request-id` header (propagated from a safe incoming
|
|
`x-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_LEVEL` env var (default `info`); logs are JSON only.
|
|
|
|
## Database (local dev)
|
|
|
|
```bash
|
|
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
|
|
|
|
```text
|
|
<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
|
|
|
|
```text
|
|
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:boundaries` enforces these rules.
|