Files
2026-08-17 22:23:10 +02:00

2.9 KiB

DESIGN — F-003 HTTP foundation and request context

Architecture

All cross-cutting HTTP behavior lives in explicit, readable code:

  • src/infrastructure/logging/logger.ts — pino factory. JSON only, level from LOG_LEVEL (default info), base field service. Accepts an optional writable destination so tests can capture output without transports (no magic).
  • src/shared/errors.tsAppError class (statusCode, code, message) and the single errorEnvelope(statusCode, code, message, requestId) builder. Envelope v2: { error: { statusCode, code, message }, requestId }.
  • src/shared/http-input.tsparseJson(schema, input) using zod. On failure throws AppError(400, VALIDATION_ERROR) carrying field-level issues (field paths only, never internal state).
  • src/app/build-app.ts — composition root wires everything explicitly:
    • Fastify genReqId: trust incoming x-request-id only if it matches ^[A-Za-z0-9._-]{1,128}$; otherwise generate crypto.randomUUID().
    • onRequest hook: set x-request-id response header, start timer.
    • onResponse hook: one JSON log line per request { requestId, method, url, statusCode, durationMs }.
    • setErrorHandler: map AppError and Fastify errors to the envelope. 4xx expose their message; >=500 always returns generic Internal Server Error. Errors are logged with stack server-side, tagged with requestId.
    • setNotFoundHandler: envelope with code NOT_FOUND + requestId.
  • src/infrastructure/http/server.ts — startup log lines through the logger.

Request id policy

  • Propagation is opt-in and sanitized; anything suspicious is replaced by a fresh UUID.
  • Response header name: x-request-id.

Validation policy

  • Validation is explicit per route: call parseJson(schema, body) inside the handler.
  • No schema decorators, no magic body binding. The F-003 deliverable is the hook (helper + envelope integration), exercised by composition tests with a test-only route registered on the built app.

Backward compatibility

  • GET /health response body unchanged; envelope gains fields (additive).

Files

File Role
src/infrastructure/logging/logger.ts pino factory
src/shared/errors.ts AppError + envelope v2
src/shared/http-input.ts zod parse helper
src/app/build-app.ts hooks, genReqId, handlers
src/infrastructure/http/server.ts startup logging
src/app/tests/http-foundation.test.ts composition tests (request id, logs, envelope, no leak)
src/shared/tests/http-input.test.ts helper unit tests

Risks / mitigations

  • Double logging (fastify internal + hooks): keep logger: false on Fastify, log only via explicit hooks.
  • Header injection through propagated id: strict regex + length cap.
  • 5xx leaking internals: handler returns fixed generic message for anything >=500; tests assert absence of thrown message/stack.