Production-ready skeleton for Node.js + TypeScript services with hardened security defaults.
A batteries-included starting point for production Node.js services. Out of the box you get a hardened Express setup, structured JSON logging, encrypted-response support, Docker hardening, request tracing, graceful shutdown and a CI pipeline wired for SonarCloud / Snyk / CodeQL / njsscan / Code Climate.
- Quick start – TypeScript, ESLint flat config, Prettier, Husky, Jest with coverage.
- Hardened security defaults
- Helmet (HSTS in prod),
x-powered-bydisabled,x-request-idpropagation. - Allow-list driven CORS via
CORS_ORIGINS. - Global rate limiter (
express-rate-limit, draft-7 standard headers). - Body size capped at 1 MB by default (configurable via
BODY_LIMIT). - PBKDF2-HMAC-SHA512 key derivation at OWASP-2023 strength (600 000 iters) + AES-256-GCM with per-message salt + IV embedded in the ciphertext.
- Production-mode filtering of system endpoints (no
process.env, network interfaces or OS user info on the wire). - Centralized config validation – the app refuses to boot with bad/missing values.
- Helmet (HSTS in prod),
- Production runtime
- Multi-stage Dockerfile on
node:22-bookworm-slim, runs as non-root withtinias PID 1, healthcheck baked in. docker-compose.ymlwithread_only,cap_drop: ALL,no-new-privilegesand resource limits.- Graceful shutdown on
SIGINT/SIGTERMwith a 10 s drain window. - Structured Winston logging (separate
error.log, JSON format, level viaLOG_LEVEL). - HTTP server timeouts (
keepAlive,headersTimeout,requestTimeout) tuned for behind-LB deployments.
- Multi-stage Dockerfile on
- Observability
/healthzand/readyzliveness/readiness endpoints (skipped from rate-limit).- Per-request id middleware (echoes/forwards
x-request-id, falls back tocrypto.randomUUID()). - Access log with status, duration, IP, user agent and request id.
- Continuous integration
- GitHub Actions matrix (Node 20.x / 22.x).
- SonarCloud, Snyk monitor, CodeQL, njsscan SARIF upload, Code Climate.
- Documentation
- Swagger UI mounted at
/docsin non-production. - Postman collection in
wiki/postman.
- Swagger UI mounted at
| Package | Purpose |
|---|---|
express |
HTTP framework |
helmet |
Secure HTTP headers |
cors |
Cross-origin allow-list |
compression |
gzip / brotli compression |
express-rate-limit |
Per-IP rate limiting |
winston |
Structured JSON logging |
dotenv |
.env loading |
swagger-ui-express |
API docs in non-prod |
http-status-codes |
Symbolic HTTP status codes |
- Node.js >= 20.x (tested on 20 and 22)
- npm >= 10
git clone https://github.com/santoshshinde2012/node-boilerplate.git
cd node-boilerplate
cp .env.example .env
npm install
npm run devnpm ci --omit=dev # or `npm install` for full local dev
npm run build
NODE_ENV=production npm run startAlways populate .env from .env.example before going to production. The boot will fail-fast if APPLY_ENCRYPTION=true is set without a valid SECRET_KEY (>= 16 chars).
docker compose up --build
# or
docker build -t node-boilerplate .
docker run --rm -p 8080:8080 --env-file .env node-boilerplateThe runtime image is multi-stage, runs as user nodeuser (uid 1001), drops all Linux capabilities and exposes a HEALTHCHECK against /healthz.
All configuration lives in environment variables (see .env.example).
| Variable | Default | Notes |
|---|---|---|
NODE_ENV |
development |
One of development, test, staging, production. Legacy prod aliased to production. |
PORT |
8080 |
TCP port. |
LOG_LEVEL |
info (prod) / debug |
Winston level. |
APPLY_ENCRYPTION |
false |
Encrypt response payloads on the wire. |
SECRET_KEY |
none | Required when encryption is enabled. Min 16 chars. |
ENCRYPTION_SALT |
none | Optional fixed salt; otherwise a per-message salt is embedded in the ciphertext. |
CORS_ORIGINS |
empty | Comma-separated allow-list. Use * for any (dev only). |
BODY_LIMIT |
1mb |
Max JSON / urlencoded body size. |
RATE_LIMIT_WINDOW_MS |
60000 |
Rate-limiter window. |
RATE_LIMIT_MAX |
120 |
Max requests per IP per window. |
TRUST_PROXY |
false |
Set to true when running behind a trusted reverse proxy (nginx/ELB/Cloudflare). |
EXPOSE_SYSTEM_ROUTES |
true (non-prod) |
Disable diagnostic /v1/system/* endpoints. |
| Path | Description |
|---|---|
src/App.ts |
Express app composition (middleware + routes + error handler). |
src/server.ts |
HTTP server bootstrap and graceful shutdown. |
src/config/ |
Centralized, validated environment config. |
src/abstractions/ |
Shared abstract classes and interfaces (e.g. ApiError). |
src/components/ |
Feature controllers grouped by domain. |
src/lib/ |
Reusable utilities (logger, crypto). |
src/middleware/ |
Cross-cutting middleware (request id, request logger, error handler). |
src/types/ |
Shared TypeScript types. |
src/utils/ |
Pure helper functions. |
tests/unit-tests/ |
Jest unit tests. |
tests/integration-tests/ |
Supertest integration tests. |
wiki/ |
Diagrams, instructions, Postman collection. |
Dockerfile / docker-compose.yml |
Hardened container build. |
| Method | Path | Notes |
|---|---|---|
GET |
/ |
Liveness ping (returns { "message": "base path" }). |
GET |
/healthz |
Health probe (uptime + timestamp). |
GET |
/readyz |
Readiness probe. |
GET |
/web |
Demo of header-based auth (requires x-internal-authorization and Authorization). |
GET |
/docs |
Swagger UI (non-production only). |
GET |
/v1/system/info |
System info (filtered in production). |
GET |
/v1/system/time |
Server time. |
GET |
/v1/system/usage |
Process + system memory and CPU usage. |
GET |
/v1/system/process |
Process info (filtered in production). |
GET |
/v1/system/error |
Sample error path. |
Every response includes the x-request-id header (echoed if the client supplied one and it matches ^[A-Za-z0-9._-]{1,128}$, otherwise a freshly minted UUID).
Set APPLY_ENCRYPTION=true and provide SECRET_KEY. Responses produced via BaseController#send are then base64 AES-256-GCM ciphertexts of the form:
salt(16) || ciphertext || iv(12) || tag(16)
The salt is randomized per-message and embedded so decrypt() works across processes/hosts without shared in-memory state. PBKDF2 derivation uses 600 000 iterations of HMAC-SHA512 (OWASP 2023 guidance).
- Swagger UI:
${host}/docs(only mounted whenNODE_ENV !== 'production'). - Postman collection:
wiki/postman/node-boilerplate.postman_collection.json– import and setBASE_URL.
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run format # prettier
npm test # jest with coverage
npm run audit:prod # npm audit --omit=dev --audit-level=highCI runs all of the above plus SonarCloud / Snyk / CodeQL / njsscan / Code Climate.
See SECURITY.md for how to responsibly report a vulnerability.
- Skeleton for Node.js Apps written in TypeScript
- Setup Eslint Prettier and Husky in Node JS Typescript Project
- Express production best practices: security
- OWASP Password Storage Cheat Sheet
- Node.js secure coding
chmod ug+x .husky/*
chmod ug+x .git/hooks/*- Skeleton for Node.js Apps written in TypeScript (with Setup Instructions for ESLint, Prettier, and Husky)
- Global Error and Response Handler in Node JS with Express and Typescript
- Testing with Jest in TypeScript and Node.js for Beginners
- Static Code Analysis for Node.js and TypeScript Project using SonarQube
- Visualization of Node.js Event Emitter


