Home›JavaScript›Node.js Production Best Practices and Checklist
Advanced
7 min · 2026-07-12
Node.js Production Best Practices and Checklist
Node.js production checklist: environment configuration, logging, security headers, error handling, process management, monitoring, and deployment best practices..
N
NarenFounder & Principal Engineer
20+ years shipping production JavaScript and front-end systems at scale. Everything here is grounded in real deployments.
What is Node.js Production Best Practices and Checklist?
Production-ready Node.js requires configuration across multiple dimensions: environment management (validated env vars, secrets management), logging (structured JSON, log levels, correlation IDs), security (Helmet headers, rate limiting, input validation, dependency auditing), error handling (global error handler, uncaught exception handler, unhandled rejection handler), process management (PM2 or container orchestration with restart policies), monitoring (health checks, metrics, distributed tracing), and deployment (zero-downtime, database migrations, feature flags). The production checklist is a living document that teams use for deployment readiness reviews and incident post-mortems.
★
Think of your Node.js app like a food truck.
Plain-English First
Think of your Node.js app like a food truck. You can cook fast, but if you don't have a checklist—like keeping ingredients cold, cleaning the grill, and having a backup generator—you'll serve bad food or shut down on a busy day. Production best practices are that checklist: they keep your app running smoothly under pressure.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
Your Node.js application passed all tests and code reviews. Two hours into production, the health check endpoint returns 503, logs are streaming to stdout with no log rotation, environment variables are hardcoded in the source code, and the database connection pool is exhausted because pool size was never configured. A production checklist transforms deployment from guesswork into a repeatable process. This article compiles every check you need before going live: security, performance, observability, reliability, and operational readiness.
1. Process Management and Clustering
In production, a single Node.js process is a single point of failure. Use a process manager like PM2 to run your app in cluster mode, spawning multiple instances across CPU cores. This ensures zero-downtime restarts, graceful shutdown, and automatic recovery from crashes. Configure instances: 'max' to utilize all cores. Set max_memory_restart to prevent memory leaks from taking down the server. Always use --max-old-space-size to limit V8 heap. Without clustering, a single uncaught exception or memory spike kills your entire service. PM2 also provides log management and monitoring hooks.
The built-in cluster module lacks production features like graceful shutdown, log aggregation, and health checks. PM2 or similar tools are battle-tested.
📊 Production Insight
We once had a memory leak that brought down a single-process server every 6 hours. Clustering with PM2 and max_memory_restart auto-restarted workers, keeping the service alive while we fixed the leak.
🎯 Key Takeaway
Always run Node.js in cluster mode with a process manager to ensure high availability and resource utilization.
thecodeforge.io
Nodejs Production Checklist
2. Environment Configuration Management
Never hardcode secrets or environment-specific values. Use environment variables with a library like dotenv for local development, but in production, inject variables via the deployment platform (e.g., Kubernetes secrets, AWS Parameter Store). Validate all required variables at startup using a schema (e.g., joi or env-var). This prevents silent failures when a config is missing. Also, separate config by environment: development, staging, production. Use a single source of truth like a config module that reads from process.env and exports typed values. Avoid using .env files in production — they can be accidentally committed.
Commit a template .env.example with dummy values so new developers know what variables are needed. Never commit actual .env files.
📊 Production Insight
A missing database URL caused our staging environment to connect to production DB for 10 minutes. Validation would have caught it immediately.
🎯 Key Takeaway
Validate all environment variables at startup to fail fast and avoid runtime surprises.
3. Error Handling and Uncaught Exceptions
Node.js crashes on uncaught exceptions. Always use a global error handler for uncaught exceptions and unhandled promise rejections. However, these handlers should log the error and then gracefully shut down the process — the process is in an unknown state. Use a process manager to restart. For operational errors (e.g., invalid input), use a centralized error-handling middleware in Express. Return consistent JSON error responses with appropriate HTTP status codes. Never expose stack traces in production. Use libraries like http-errors to create error objects. Also, handle async errors by wrapping route handlers with a catch-all.
Catching errors without logging or rethrowing leads to silent failures. Always log and then decide: recover or crash.
📊 Production Insight
An unhandled promise rejection in a payment callback caused silent data loss. Adding the global handler and crashing forced a restart and alerted us to the bug.
🎯 Key Takeaway
Handle all errors centrally, log them, and crash on uncaught exceptions — let the process manager restart.
thecodeforge.io
Nodejs Production Checklist
4. Logging and Monitoring
Console.log is not enough for production. Use structured logging with a library like pino or winston. Log in JSON format so log aggregators (ELK, Datadog) can parse them. Include correlation IDs for request tracing. Log at appropriate levels: error, warn, info, debug. Never log sensitive data (passwords, tokens). Set up health check endpoints (e.g., /health) that return status of database, cache, and external services. Use APM tools like New Relic or Sentry for performance monitoring. Monitor memory usage, event loop lag, and garbage collection. Alerts should trigger on error rate spikes or high latency.
Generate a unique ID per request (e.g., uuid) and pass it to all logs and downstream services. This lets you trace a single request across microservices.
📊 Production Insight
Without correlation IDs, we spent hours correlating logs from different services during an outage. Now we trace requests end-to-end in seconds.
🎯 Key Takeaway
Structured JSON logging with correlation IDs is essential for debugging production issues.
5. Security Best Practices
Production Node.js apps are frequent targets. Always use Helmet to set secure HTTP headers. Validate and sanitize all user input to prevent injection attacks. Use parameterized queries for databases. Implement rate limiting to prevent brute-force attacks. Use express-rate-limit or a reverse proxy like Nginx. Set up CORS properly — don't use wildcard in production. Use environment-specific CORS origins. Encrypt sensitive data at rest and in transit. Use bcrypt for password hashing. Keep dependencies updated with npm audit and use a tool like Snyk. Disable X-Powered-By header. Use csurf for CSRF protection if using cookies.
Use well-vetted libraries like bcrypt for passwords and crypto for tokens. Custom implementations often have vulnerabilities.
📊 Production Insight
We once had a DDoS attack that hit our login endpoint. Rate limiting at the reverse proxy level saved us, but we also added it in the app as a second layer.
🎯 Key Takeaway
Apply security headers, input validation, rate limiting, and keep dependencies updated to prevent common attacks.
6. Database Connection Management
Database connections are expensive. Use a connection pool (e.g., pg-pool for PostgreSQL, mongoose for MongoDB) to reuse connections. Set pool size based on your database's max connections and your app's concurrency. Monitor pool usage — if you exhaust connections, requests will queue or fail. Use retry logic with exponential backoff for transient failures. Always close connections gracefully on shutdown. For read-heavy workloads, consider read replicas. Use environment-specific pool settings: smaller pool in development, larger in production. Also, use connection strings with SSL enabled in production.
src/db.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const { Pool } = require('pg');
const pool = newPool({
connectionString: process.env.DB_URL,
max: 20, // max connections in pool
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: true } : false
});
// Query wrapper with retryasyncfunctionquery(text, params, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const result = await pool.query(text, params);
return result;
} catch (err) {
if (i === retries - 1) throw err;
console.warn(`Query failed, retrying (${i + 1}/${retries})`);
awaitnewPromise(res => setTimeout(res, 1000 * Math.pow(2, i)));
}
}
}
// Graceful shutdown
process.on('SIGTERM', async () => {
await pool.end();
process.exit(0);
});
module.exports = { query, pool };
Output
Pool maintains up to 20 connections. If a query fails, it retries up to 3 times with exponential backoff (1s, 2s, 4s). On SIGTERM, pool closes gracefully.
Use pool.totalCount and pool.waitingCount to detect connection leaks. Set up alerts when waiting count exceeds a threshold.
📊 Production Insight
A connection leak due to unclosed transactions exhausted our pool, causing a 5-minute outage. Adding monitoring and pool limits prevented recurrence.
🎯 Key Takeaway
Use connection pooling with retry logic and graceful shutdown to handle database connections reliably.
7. Caching Strategies
Caching reduces load on databases and improves response times. Use in-memory caching (e.g., node-cache) for single-instance apps, but for clustered or multi-server setups, use a distributed cache like Redis. Cache database query results, computed values, and API responses. Set appropriate TTLs based on data staleness tolerance. Use cache-aside pattern: check cache first, if miss, fetch from source and populate cache. Implement cache invalidation carefully — stale data can cause bugs. For high-traffic endpoints, consider write-through or write-behind caching. Monitor cache hit rates; low hit rates indicate poor cache strategy.
When data updates, you must invalidate or update the cache. Use event-driven invalidation (e.g., publish update event) or set short TTLs.
📊 Production Insight
We cached user profile data with a 1-hour TTL. After a bulk update, users saw stale data for an hour. Now we invalidate cache on write.
🎯 Key Takeaway
Use a distributed cache like Redis with appropriate TTLs and cache-aside pattern to reduce database load.
8. Graceful Shutdown and Health Checks
When your app receives a termination signal (SIGTERM from Kubernetes, PM2, etc.), you must shut down gracefully: stop accepting new requests, finish in-flight requests, close database connections, and then exit. Implement a health check endpoint that returns liveness and readiness. Liveness indicates the process is alive; readiness indicates it can serve traffic (e.g., database is connected). Kubernetes uses these to restart pods or stop routing traffic. Use http-shutdown or manual server closing. Set a timeout for forced exit to prevent hanging.
If you don't handle SIGTERM, the process is killed forcefully, potentially corrupting data or leaving connections open.
📊 Production Insight
During a Kubernetes rolling update, pods without graceful shutdown caused 502 errors. Adding shutdown handlers eliminated the issue.
🎯 Key Takeaway
Implement graceful shutdown and health checks to ensure zero-downtime deployments and self-healing.
9. Dependency Management and CI/CD
Lock your dependencies with package-lock.json or yarn.lock. Use npm ci in CI for deterministic installs. Regularly run npm audit and fix vulnerabilities. Use a tool like snyk or dependabot for automated updates. In CI, run linting, tests, and security scans. Build artifacts (e.g., transpiled code) should be created in CI, not committed. Use multi-stage Docker builds to keep images small. Tag images with git commit hash for traceability. Deploy using blue-green or rolling updates. Automate rollback if health checks fail.
DockerfileDOCKERFILE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM node:18-alpine AS builder
WORKDIR /app
COPYpackage*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY dist/ ./dist/
COPYpackage.json ./
EXPOSE3000USER node
CMD ["node", "dist/server.js"]
Output
Final image is ~150MB (vs 1GB if using full Node image). Only production dependencies and compiled code are included.
💡Use npm ci, not npm install
npm ci installs from lockfile exactly, fails if lockfile is out of sync, and is faster. Use it in CI and production builds.
📊 Production Insight
A developer accidentally committed a dev dependency that introduced a vulnerability. npm audit in CI caught it before deployment.
🎯 Key Takeaway
Lock dependencies, automate security scans, and use multi-stage Docker builds for reliable and secure deployments.
10. Performance Optimization and Profiling
Profile your app under load to find bottlenecks. Use Node.js built-in profiler (--prof) or tools like clinic.js. Common issues: synchronous operations blocking the event loop, excessive garbage collection, memory leaks, and slow database queries. Use async for I/O. Avoid JSON.parse on large payloads in the main thread — offload to worker threads if needed. Use streaming for large responses. Implement response compression with compression middleware. Set NODE_ENV=production to enable optimizations (view caching, etc.). Use clinic to generate flamegraphs. Monitor event loop lag with process.hrtime.
Don't guess bottlenecks. Use profiling tools to identify actual hot spots. Premature optimization wastes time.
📊 Production Insight
A synchronous JSON.parse on a 10MB request body blocked the event loop for 2 seconds, causing timeouts. We moved parsing to a worker thread.
🎯 Key Takeaway
Profile your app, use compression, monitor event loop lag, and avoid blocking the event loop.
11. Testing for Production Reliability
Unit tests alone are not enough. Write integration tests that test your API endpoints with a real database. Use supertest for HTTP testing. Write contract tests for microservices. Implement smoke tests that run after deployment to verify the app is healthy. Use load testing (e.g., artillery or k6) to find breaking points. Test error scenarios: database down, external API failure, invalid input. Use test containers for database dependencies. Aim for high code coverage but focus on critical paths. Run tests in CI on every push.
tests/integration/users.test.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const request = require('supertest');
const app = require('../src/app');
const { pool } = require('../src/db');
beforeAll(async () => {
// Setup test databaseawait pool.query('CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)');
});
afterAll(async () => {
await pool.query('DROP TABLE IF EXISTS users');
await pool.end();
});
describe('POST /api/users', () => {
it('should create a user', async () => {
const res = awaitrequest(app)
.post('/api/users')
.send({ name: 'John' })
.expect(201);
expect(res.body).toHaveProperty('id');
});
it('should return 400 for invalid name', async () => {
awaitrequest(app)
.post('/api/users')
.send({ name: '' })
.expect(400);
});
});
Output
Tests pass: 2 passed, 0 failed. Database is cleaned up after tests.
Run a real PostgreSQL in a Docker container for integration tests. Avoid mocking the database — it hides real issues.
📊 Production Insight
Our unit tests passed but integration tests failed because of a schema mismatch. Now we run integration tests in CI with a real database.
🎯 Key Takeaway
Write integration and smoke tests that run against real dependencies to catch production-like failures.
12. Deployment and Rollback Strategy
Use immutable deployments: build a new artifact (Docker image) for each version. Tag images with git commit hash. Use blue-green or canary deployments to minimize risk. Always have a rollback plan: keep the previous version running and switch back if health checks fail. Use feature flags to toggle features without redeploying. Automate the deployment pipeline with CI/CD (e.g., GitHub Actions, GitLab CI). Monitor error rates and latency after deployment. If error rate spikes, trigger automatic rollback. Document the rollback procedure.
On push to main, Docker image is built and pushed, Kubernetes deployment updated, smoke test runs. If smoke test fails, deployment is rolled back.
⚠ Always test rollback
Practice rollback in staging. A rollback that doesn't work is worse than no rollback. Ensure database migrations are backward-compatible.
📊 Production Insight
A bad deployment caused 500 errors for 10 minutes before manual rollback. Now we have automated rollback triggered by health check failures.
🎯 Key Takeaway
Use immutable deployments with automated rollback on health check failure to ensure safe releases.
Security Headers with Helmet.js
Helmet.js is a middleware that sets various HTTP security headers to protect your Express app from common web vulnerabilities. It bundles 15 smaller middleware functions, including CSP, X-Frame-Options, and X-Content-Type-Options. In production, you should configure Helmet with strict policies. For example, set Content-Security-Policy to restrict script sources and disable inline scripts unless hashed. Use helmet.contentSecurityPolicy() with directives like defaultSrc: ["'self'"] and scriptSrc: ["'self'", "'strict-dynamic'"]. Avoid using 'unsafe-inline' in production. Also, enable referrerPolicy: 'same-origin' and hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }. Test your headers with securityheaders.com. Remember that overly strict CSP can break third-party scripts, so iterate carefully.
Start with report-only mode (contentSecurityPolicy: { useDefaults: false, directives: {...}, reportOnly: true }) to catch violations without blocking.
📊 Production Insight
Use Helmet's defaults for most headers, but always customize CSP for your app's specific resource needs.
🎯 Key Takeaway
Helmet.js sets essential security headers; configure CSP strictly but test in report-only mode first.
Rate Limiting with express-rate-limit
Rate limiting protects your API from abuse and brute-force attacks. Use express-rate-limit middleware to cap requests per IP. In production, set a generous limit for general endpoints (e.g., 100 requests per 15 minutes) and stricter limits for auth routes (e.g., 5 attempts per 15 minutes). Configure the middleware with windowMs, max, and a custom message. Use keyGenerator to rate-limit by user ID if authenticated. For distributed environments, use an external store like Redis via rate-limit-redis to share state across instances. Always return a Retry-After header. Monitor rate limit hits via logs or metrics. Avoid rate limiting health check endpoints. Example: create a separate limiter for /api/auth/login with max: 5 and windowMs: 15 60 1000.
Use skip function to exempt health endpoints: skip: (req) => req.path === '/health'.
📊 Production Insight
Set rate limits based on expected traffic patterns; monitor and adjust after load testing.
🎯 Key Takeaway
Rate limit aggressively on auth routes, use Redis store for distributed apps, and skip health endpoints.
CORS Configuration
Cross-Origin Resource Sharing (CORS) controls which domains can access your API. In production, never use origin: '*'. Instead, whitelist specific origins. Use the cors package with an array of allowed origins or a function that validates the request origin. For example, origin: ['https://myapp.com', 'https://admin.myapp.com']. If you need to support dynamic origins (e.g., mobile apps), validate against a regex or a list. Also set credentials: true if using cookies or authorization headers. Restrict allowed methods (GET,POST,PUT,DELETE) and headers (Content-Type,Authorization). Set maxAge to cache preflight responses (e.g., 86400 seconds). For public APIs, consider using a reverse proxy to handle CORS. Always test with different origins in staging.
The Origin header can be spoofed; use CORS as a browser-enforced policy, not as an authentication mechanism.
📊 Production Insight
Use environment variables for allowed origins to avoid hardcoding.
🎯 Key Takeaway
Whitelist specific origins, enable credentials only if needed, and cache preflight responses.
Input Validation and Sanitization
Always validate and sanitize user input to prevent injection attacks and data corruption. Use a schema validation library like Joi or Zod for request bodies, query params, and route params. Define strict schemas with expected types, lengths, and patterns. Sanitize strings to remove HTML tags using sanitize-html or xss. For MongoDB, use mongo-sanitize to prevent NoSQL injection. Never trust req.body, req.query, or req.params directly. Validate at the middleware level before any business logic. Return clear error messages but avoid exposing internal details. For file uploads, validate MIME types and file size. Use express-validator for simple cases, but prefer Joi for complex schemas. Example: validate email with Joi.string().email().required(). Always normalize Unicode characters to prevent homograph attacks.
Use sanitize-html to strip dangerous tags from user-generated content before saving to DB.
📊 Production Insight
Centralize validation logic in middleware to keep controllers clean and consistent.
🎯 Key Takeaway
Validate all input with a schema library, sanitize strings, and never trust raw request data.
Gzip Compression
Enable gzip compression to reduce response size and improve load times. Use the compression middleware in Express. In production, set compression level to 6 (balance between speed and ratio). Filter out already compressed responses (e.g., images, videos) using the filter option. For high-traffic apps, offload compression to a reverse proxy like Nginx. Test with curl -H "Accept-Encoding: gzip" -o /dev/null -w "%{size_download}" to compare sizes. Be aware that compression adds CPU overhead; monitor server load. For dynamic content, cache compressed responses in Redis. Example: app.use(compression({ level: 6, threshold: 1024 })) compresses responses larger than 1KB. Always set Vary: Accept-Encoding header (handled automatically).
The default filter skips responses with Content-Type image/, video/, etc.
📊 Production Insight
Monitor CPU usage; if high, consider using Brotli compression (supported by modern browsers) for better ratios.
🎯 Key Takeaway
Enable gzip compression with level 6 and threshold 1KB; offload to reverse proxy for high traffic.
thecodeforge.io
Nodejs Production Checklist
Dependency Vulnerability Scanning
Regularly scan your dependencies for known vulnerabilities using npm audit and Snyk. Run npm audit in CI to fail builds on high-severity issues. Use npm audit fix to auto-fix where possible, but review changes. For deeper scanning, integrate Snyk into your pipeline: snyk test and snyk monitor. Snyk provides real-time alerts and fix PRs. Also scan your Docker images with snyk container test or Trivy. In your Dockerfile, use multi-stage builds to minimize attack surface. Pin base image versions (e.g., node:18-alpine). Regularly update dependencies with npm update or tools like Renovate. For production, consider using a private registry with vulnerability scanning. Example CI step: npm audit --audit-level=high exits with code 1 if any high severity found.
Fails CI if any high severity vulnerability found.
⚠ Don't Ignore Dev Dependencies
Vulnerabilities in devDependencies can still affect your build pipeline; scan them too.
📊 Production Insight
Automate dependency updates with Renovate or Dependabot, and review changelogs before merging.
🎯 Key Takeaway
Run npm audit in CI, integrate Snyk for continuous monitoring, and scan Docker images.
● Production incidentPOST-MORTEMseverity: high
The Silent Memory Leak That Took Down Our API Every 48 Hours
Symptom
API response times gradually increased over 48 hours, then the process ran out of memory and crashed. Restarting fixed it temporarily.
Assumption
The team assumed it was a traffic spike or a bug in a third-party library, so they added more instances and increased memory limits.
Root cause
A new feature endpoint opened a MongoDB connection using MongoClient.connect() inside each request handler but never called client.close(). Over time, thousands of dangling connections consumed all memory.
Fix
Refactored to use a connection pool (Mongoose) that reuses connections, and added a middleware to close any open connections on response finish. Also added a memory leak detection script using heap snapshots.
Key lesson
Always use connection pooling for databases; never open a new connection per request.
Monitor memory usage and set up alerts for gradual increases.
Add automated tests that simulate long-running requests to catch resource leaks.
Use tools like clinic.js or heapdump to profile memory in staging before deploying to production.
⚙ Quick Reference
18 commands from this guide
File
Command / Code
Purpose
ecosystem.config.js
module.exports = {
1. Process Management and Clustering
configindex.js
const envalid = require('envalid');
2. Environment Configuration Management
srcmiddlewareerrorHandler.js
const createError = require('http-errors');
3. Error Handling and Uncaught Exceptions
srclogger.js
const pino = require('pino');
4. Logging and Monitoring
srcapp.js
const express = require('express');
5. Security Best Practices
srcdb.js
const { Pool } = require('pg');
6. Database Connection Management
srccache.js
const redis = require('redis');
7. Caching Strategies
srcserver.js
const http = require('http');
8. Graceful Shutdown and Health Checks
Dockerfile
FROM node:18-alpine AS builder
9. Dependency Management and CI/CD
srcmiddlewareperformance.js
const compression = require('compression');
10. Performance Optimization and Profiling
testsintegrationusers.test.js
const request = require('supertest');
11. Testing for Production Reliability
.githubworkflowsdeploy.yml
name: Deploy
12. Deployment and Rollback Strategy
app.js
const helmet = require('helmet');
Security Headers with Helmet.js
rateLimiter.js
const rateLimit = require('express-rate-limit');
Rate Limiting with express-rate-limit
cors.js
const cors = require('cors');
CORS Configuration
validation.js
const Joi = require('joi');
Input Validation and Sanitization
app.js
const compression = require('compression');
Gzip Compression
.githubworkflowsci.yml
name: CI
Dependency Vulnerability Scanning
Key takeaways
1
Process Management
Always run Node.js in cluster mode with a process manager like PM2 to ensure high availability and resource utilization.
2
Error Handling
Handle all errors centrally, log them, and crash on uncaught exceptions; let the process manager restart the app.
3
Security
Apply security headers, input validation, rate limiting, and keep dependencies updated to prevent common attacks.
4
Deployment
Use immutable deployments with automated rollback on health check failure to ensure safe releases.
5
Security Headers
Use Helmet.js with a strict CSP (report-only first), HSTS, and Referrer-Policy. Customize beyond defaults.
6
Rate Limiting
Apply express-rate-limit with Redis store for distributed apps; set stricter limits on auth routes and skip health endpoints.
7
Dependency Scanning
Run npm audit in CI, integrate Snyk for continuous monitoring, and scan Docker images with Trivy or Snyk.
8
Security Headers
Use Helmet.js to set HTTP security headers. Customize CSP with nonces for SPAs and test in report-only mode.
9
Input Validation
Always validate and sanitize inputs server-side using a schema library. Whitelist allowed values and escape output.
10
Dependency Scanning
Regularly scan dependencies with npm audit and Snyk. Integrate into CI and automate updates with Dependabot.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the purpose of using a process manager like PM2 in production?
Q02SENIOR
How would you handle uncaught exceptions in a Node.js production applica...
Q03SENIOR
Explain the event loop and how a long-running synchronous operation can ...
Q04SENIOR
What are some common security vulnerabilities in Node.js and how do you ...
Q05SENIOR
How do you monitor a Node.js application in production?
Q06SENIOR
Describe a strategy for zero-downtime deployments with Node.js.
Q01 of 06JUNIOR
What is the purpose of using a process manager like PM2 in production?
ANSWER
PM2 keeps your Node.js app alive by automatically restarting it if it crashes, managing logs, and enabling zero-downtime deployments. It also helps with clustering to utilize multiple CPU cores.
Q02 of 06SENIOR
How would you handle uncaught exceptions in a Node.js production application?
ANSWER
Use process.on('uncaughtException') to log the error and gracefully shut down the process, then rely on a process manager like PM2 to restart it. Never try to resume the app after an uncaught exception because the state is unreliable.
Q03 of 06SENIOR
Explain the event loop and how a long-running synchronous operation can block it. How do you avoid this in production?
ANSWER
The event loop processes callbacks and I/O. A synchronous CPU-intensive task blocks the loop, freezing the app. Avoid by offloading to worker threads, using child processes, or breaking the task into async chunks with setImmediate().
Q04 of 06SENIOR
What are some common security vulnerabilities in Node.js and how do you mitigate them?
ANSWER
Common issues: injection attacks (use parameterized queries), XSS (sanitize output), and dependency vulnerabilities (run npm audit regularly). Also set HTTP headers like Helmet and validate input with libraries like Joi.
Q05 of 06SENIOR
How do you monitor a Node.js application in production?
ANSWER
Use APM tools like New Relic or Datadog, collect metrics (CPU, memory, event loop lag) via process module, and set up structured logging with Winston or Pino. Also monitor for memory leaks using heap snapshots.
Q06 of 06SENIOR
Describe a strategy for zero-downtime deployments with Node.js.
ANSWER
Use a load balancer and run two instances of the app. Deploy the new version to one instance, wait for it to be healthy, then switch traffic. Tools like PM2 or Kubernetes rolling updates handle this. Ensure database migrations are backward-compatible.
01
What is the purpose of using a process manager like PM2 in production?
JUNIOR
02
How would you handle uncaught exceptions in a Node.js production application?
SENIOR
03
Explain the event loop and how a long-running synchronous operation can block it. How do you avoid this in production?
SENIOR
04
What are some common security vulnerabilities in Node.js and how do you mitigate them?
SENIOR
05
How do you monitor a Node.js application in production?
SENIOR
06
Describe a strategy for zero-downtime deployments with Node.js.
SENIOR
FAQ · 12 QUESTIONS
Frequently Asked Questions
01
Why should I use a process manager like PM2 instead of the built-in cluster module?
The built-in cluster module lacks production features such as graceful shutdown, log aggregation, automatic restarts, and health checks. PM2 provides these out of the box, plus monitoring and easy scaling.
Was this helpful?
02
How do I handle environment variables in production without using .env files?
Inject environment variables via your deployment platform (e.g., Kubernetes secrets, AWS Parameter Store, Docker environment variables). Use a config module that validates all required variables at startup and fails fast if any are missing.
Was this helpful?
03
What is the difference between liveness and readiness probes in Kubernetes?
Liveness probe checks if the process is alive (e.g., responds to /health). If it fails, Kubernetes restarts the pod. Readiness probe checks if the app can serve traffic (e.g., database is connected). If it fails, traffic is stopped from reaching the pod.
Was this helpful?
04
How do I prevent memory leaks in Node.js production apps?
Use tools like clinic or heapdump to profile memory. Avoid global variables, close database connections, use streams for large data, and set --max-old-space-size. Monitor memory usage and set alerts.
Was this helpful?
05
Should I use npm install or npm ci in production?
Use npm ci because it installs exact versions from the lockfile, is faster, and fails if the lockfile is out of sync with package.json. This ensures deterministic builds.
Was this helpful?
06
What is the best way to log in production?
Use structured JSON logging with a library like Pino. Include correlation IDs, log levels, and redact sensitive data. Send logs to a centralized aggregator like ELK or Datadog for analysis.
Was this helpful?
07
Should I use Helmet.js defaults or customize them for production?
Start with Helmet's defaults for most headers, but always customize Content-Security-Policy (CSP) to match your app's resources. Default CSP allows 'unsafe-inline' which is insecure. Use report-only mode first to test. Also enable HSTS with includeSubDomains and preload if you serve over HTTPS exclusively.
Was this helpful?
08
How do I handle CORS for a mobile app or server-to-server communication?
For mobile apps, the Origin header may be undefined or spoofed. Use a function that validates against a whitelist, but don't rely solely on CORS for security. For server-to-server, consider using API keys or mutual TLS instead of CORS. Alternatively, set origin: true to echo back the request origin, but only if you trust all clients.
Was this helpful?
09
What's the best way to validate and sanitize user input in Express?
Use Joi or Zod for schema validation. Define strict schemas with types, lengths, and regex patterns. Sanitize strings with sanitize-html to remove HTML tags. For MongoDB, use mongo-sanitize to prevent NoSQL injection. Always validate at the middleware layer before any business logic. Never trust raw request data.
Was this helpful?
10
How do I configure Helmet.js for a single-page application (SPA) that uses inline scripts?
For SPAs, you need to relax the CSP to allow inline scripts and styles. Use 'unsafe-inline' in script-src and style-src directives, but consider using nonces or hashes for better security. Example: helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", (req, res) => 'nonce-${res.locals.nonce}'], styleSrc: ["'self'", "'unsafe-inline'"] } }). Generate a unique nonce per request and pass it to your template.
Was this helpful?
11
Should I use npm audit or Snyk for vulnerability scanning?
Use both. npm audit is free and built-in, good for quick checks. Snyk offers deeper analysis, prioritization, and fix advice. For CI, start with npm audit --audit-level=high and add Snyk for comprehensive coverage. Snyk also supports container scanning and infrastructure-as-code.
Was this helpful?
12
How do I handle CORS preflight caching to reduce OPTIONS requests?
Set the Access-Control-Max-Age header in your CORS response to cache preflight results. In the cors package, use the maxAge option: cors({ maxAge: 86400 }) to cache for 24 hours. This reduces the number of OPTIONS requests from the client. Ensure your server responds quickly to OPTIONS requests.