- 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
2.9 KiB
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 fromLOG_LEVEL(defaultinfo), base fieldservice. Accepts an optional writable destination so tests can capture output without transports (no magic).src/shared/errors.ts—AppErrorclass (statusCode, code, message) and the singleerrorEnvelope(statusCode, code, message, requestId)builder. Envelope v2:{ error: { statusCode, code, message }, requestId }.src/shared/http-input.ts—parseJson(schema, input)using zod. On failure throwsAppError(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 incomingx-request-idonly if it matches^[A-Za-z0-9._-]{1,128}$; otherwise generatecrypto.randomUUID(). onRequesthook: setx-request-idresponse header, start timer.onResponsehook: one JSON log line per request{ requestId, method, url, statusCode, durationMs }.setErrorHandler: mapAppErrorand Fastify errors to the envelope. 4xx expose their message; >=500 always returns genericInternal Server Error. Errors are logged with stack server-side, tagged with requestId.setNotFoundHandler: envelope with codeNOT_FOUND+ requestId.
- Fastify
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 /healthresponse 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: falseon 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.