paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
85 lines (54 loc) • 4.31 kB
Markdown
# Node.js Service Conventions
## Process Lifecycle
Define clear phases for your service process:
1. **Startup** — load and validate configuration, initialise connections, register signal handlers.
2. **Ready** — signal readiness after all dependencies are connected (emit a log line, write to a readiness file, or call a health endpoint).
3. **Running** — process requests, jobs, or events.
4. **Shutdown** — drain in-flight work, close connections, flush logs, exit with code `0`.
Never start accepting work before the ready phase is complete.
## Graceful Shutdown
- Register both `SIGTERM` and `SIGINT` handlers. Kubernetes sends `SIGTERM`; Ctrl-C sends `SIGINT`.
- On signal receipt: stop accepting new work, drain queues/in-flight requests, close server and database connections, then `process.exit(0)`.
- Set a shutdown timeout (e.g., 10 seconds). If graceful shutdown takes longer, force-exit and log the reason.
- Use `process.on('unhandledRejection', ...)` and `process.on('uncaughtException', ...)` to log and exit non-zero rather than silently ignoring failures.
## Health Checks
- Expose a `/health` or `/ready` HTTP endpoint even if your service is not primarily HTTP-based.
- Liveness check (`/health`): returns `200` if the process is running.
- Readiness check (`/ready`): returns `200` only if all dependencies (database, cache, external APIs) are reachable.
- Return structured JSON (`{ "status": "ok" }`) for machine parsing.
## Environment Variable Management
- Map `process.env` to a typed config object via `zod`, `envalid`, or a hand-written validator, and import that config module everywhere instead of reading `process.env` inline.
## Logging
- Use structured JSON logging in production (e.g., `pino`). Human-readable logs in development.
- Log levels: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. Default production level: `info`.
- Include a correlation ID (request ID, job ID) in every log line for traceability.
- Never log PII (passwords, tokens, credit card numbers, email addresses) at any log level.
- Log the start and end of every significant unit of work with timing information.
## Error Handling
- Distinguish between operational errors (expected: invalid input, database timeout) and programmer errors (unexpected: null dereference, type error). Only programmer errors should crash the process.
- Wrap async entry points in try/catch or use `process.on('unhandledRejection')` as a last resort, not a first resort.
- Return machine-readable error responses from HTTP endpoints (`{ "error": "...", "code": "..." }`).
## Testing Patterns
### Unit Tests
- Test business logic in pure functions isolated from I/O. Inject dependencies (database, cache, queue client) rather than importing them directly.
- Test error paths explicitly — network timeouts, database constraint violations, queue full.
### Integration Tests
- Test the full request/response cycle against a real database and real queue (use Docker Compose in CI).
- Test graceful shutdown by sending `SIGTERM` to the running process and asserting clean exit.
- Test health check endpoints return the correct status codes under normal and degraded conditions.
## Security
### Network Binding
- In development, bind to `127.0.0.1` (localhost only). In containers, bind to `0.0.0.0`.
- Never expose management or admin endpoints on the same port as the public API without authentication.
### TLS Configuration
- Terminate TLS at the load balancer or reverse proxy in production. Do not skip TLS validation (`rejectUnauthorized: false`) in any environment.
- When TLS is terminated in the service, use only TLS 1.2+ and disable weak cipher suites.
### Secret Injection
- Inject secrets via environment variables or a secrets manager (Vault, AWS Secrets Manager, Doppler). Never hardcode secrets or commit them to the repository.
- Secrets should not appear in logs, error messages, or health check responses.
### Rate Limiting
- Apply rate limiting at the service boundary for any externally reachable endpoint.
- Use a distributed rate limiter (backed by Redis) when running multiple replicas.
### Request Validation
- Validate all inbound data against a schema before processing. Reject unknown fields.
- Set request body size limits to prevent memory exhaustion attacks.