Skip to main content 홈 크리에이터 bobmatnyc claude-mpm-skills express-production
express-production Production-ready Express.js development covering middleware architecture, error handling, security hardening, testing strategies, and deployment patterns
설치로 이동 Skills Marketplace 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill express-production명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Zip 다운로드 다운로드 중... 이 저장소의 다른 Skills LinkedIn automation via the Linked API CLI - fetch profiles, search people and companies, send messages, manage connections, create posts, react, comment, and run Sales Navigator and custom workflows. Use when the user wants to interact with LinkedIn.
Xquik X data automation API - Use REST or MCP for tweet search, user lookup, follower exports, media downloads, monitors, webhooks, giveaway draws, and confirmation-gated X actions.
MCP (Model Context Protocol) - Build AI-native servers with tools, resources, and prompts. TypeScript/Python SDKs for Claude Desktop integration.
name express-production description Production-ready Express.js development covering middleware architecture, error handling, security hardening, testing strategies, and deployment patterns user-invocable false disable-model-invocation true skill_version 2.0.0 updated_at "2025-12-03T00:00:00.000Z" tags ["express","nodejs","production","middleware","security","testing","deployment","backend"] progressive_disclosure {"entry_point":{"summary":"Production-ready Express.js framework with comprehensive middleware, security, testing, and deployment patterns","when_to_use":"Building production REST APIs, microservices, web applications requiring robust middleware architecture, security hardening, and comprehensive testing","quick_start":"1. npm install express 2. Create app.js with middleware stack 3. Implement error handling 4. Add security middleware 5. Deploy with PM2"},"references":["references/middleware-patterns.md","references/security-hardening.md","references/testing-strategies.md","references/production-deployment.md"]} context_limit 800
Express.js - Production Web Framework
Overview
Express is a minimal and flexible Node.js web application framework providing a robust set of features for web and mobile applications. This skill covers production-ready Express development including middleware architecture, structured error handling, security hardening, comprehensive testing, and deployment strategies.
Key Features :
Flexible middleware architecture with composition patterns
Centralized error handling with async support
Security hardening (Helmet, CORS, rate limiting, input validation)
Comprehensive testing with Supertest
Production deployment with PM2 clustering
Environment-based configuration
Structured logging and monitoring
Graceful shutdown patterns
Zero-downtime deployments
Installation :
npm install express
npm install express helmet cors express-rate-limit express-validator
npm install morgan winston compression
npm install dotenv
npm install -D nodemon supertest jest
npm install mongoose jsonwebtoken bcrypt
When to Use This Skill
Use this comprehensive Express skill when:
Building production REST APIs
Creating microservices architectures
Implementing secure web applications
Need flexible middleware composition
Require comprehensive error handling
Building systems requiring extensive testing
Deploying high-availability services
Need granular control over request/response lifecycle
Express vs Other Frameworks :
Express : Maximum flexibility, unopinionated, extensive ecosystem
Fastify : Performance-focused, schema-based validation
Koa : Modern async/await, minimalist
NestJS : TypeScript-first, opinionated, enterprise patterns
Quick Start
Minimal Express Server
const express = require ('express' );
app = ();
= process. . || ;
app. (express. ());
app. (express. ({ : }));
app. ( , {
res. ({ : });
});
app. ( , {
res. ({ : , : process. () });
});
app. ( {
. (err. );
res. ( ). ({ : });
});
server = app. ( , {
. ( );
});
process. ( , {
. ( );
server. ( {
. ( );
process. ( );
});
});
const
express
const
PORT
env
PORT
3000
use
json
use
urlencoded
extended
true
get
'/'
(req, res ) =>
json
message
'Hello World'
get
'/health'
(req, res ) =>
json
status
'ok'
uptime
uptime
use
(err, req, res, next ) =>
console
error
stack
status
500
json
error
'Internal server error'
const
listen
PORT
() =>
console
log
`Server running on port ${PORT} `
on
'SIGTERM'
() =>
console
log
'SIGTERM received, closing server...'
close
() =>
console
log
'Server closed'
exit
0
npm install -D nodemon
npx nodemon server.js
npm run dev
Production-Ready Server Structure project/
├── src/
│ ├── app.js # Express app factory
│ ├── server.js # Server entry point
│ ├── config/
│ │ ├── index.js # Configuration management
│ │ └── logger.js # Winston logger setup
│ ├── middleware/
│ │ ├── errorHandler.js # Centralized error handling
│ │ ├── validation.js # Input validation
│ │ ├── auth.js # Authentication middleware
│ │ └── rateLimiter.js # Rate limiting
│ ├── routes/
│ │ ├── index.js # Route aggregator
│ │ ├── users.js # User routes
│ │ └── api/ # API versioning
│ ├── controllers/
│ │ ├── userController.js
│ │ └── authController.js
│ ├── models/ # Data models
│ ├── services/ # Business logic
│ ├── utils/
│ │ ├── AppError.js # Custom error class
│ │ └── catchAsync.js # Async wrapper
│ └── tests/
│ ├── unit/
│ └── integration/
├── ecosystem.config.js # PM2 configuration
├── .env.example # Environment template
├── nodemon.json # Nodemon config
└── package.json
Middleware Architecture
Understanding Middleware Middleware functions are functions that have access to the request object (req), response object (res), and the next middleware function (next).
Application-level : app.use() or app.METHOD()
Router-level : router.use() or router.METHOD()
Error-handling : Four parameters (err, req, res, next)
Built-in : express.json(), express.static()
Third-party : helmet, cors, morgan
Proper Middleware Order const express = require ('express' );
const helmet = require ('helmet' );
const cors = require ('cors' );
const compression = require ('compression' );
const morgan = require ('morgan' );
const rateLimit = require ('express-rate-limit' );
const app = express ();
app.use (helmet ());
app.use (cors ({
origin : process.env .ALLOWED_ORIGINS ?.split (',' ) || '*' ,
credentials : true ,
methods : ['GET' , 'POST' , 'PUT' , 'DELETE' , 'PATCH' ],
allowedHeaders : ['Content-Type' , 'Authorization' ]
}));
const limiter = rateLimit ({
windowMs : 15 * 60 * 1000 ,
max : 100 ,
message : 'Too many requests from this IP'
});
app.use ('/api/' , limiter);
app.use (express.json ({ limit : '10mb' }));
app.use (express.urlencoded ({ extended : true , limit : '10mb' }));
app.use (compression ());
if (process.env .NODE_ENV !== 'production' ) {
app.use (morgan ('dev' ));
} else {
app.use (morgan ('combined' ));
}
app.use (express.static ('public' ));
app.use (require ('./middleware/requestId' ));
app.use (require ('./middleware/timing' ));
app.use ('/api/v1/users' , require ('./routes/users' ));
app.use ('/api/v1/posts' , require ('./routes/posts' ));
app.use ((req, res ) => {
res.status (404 ).json ({ error : 'Route not found' });
});
app.use (require ('./middleware/errorHandler' ));
app.use ('/api/users' , userRoutes);
app.use (helmet ());
app.use (errorHandler);
app.use ('/api/users' , userRoutes);
app.use ('/api/users' , userRoutes);
app.use (express.json ());
Custom Middleware Patterns
const { v4 : uuidv4 } = require ('uuid' );
module .exports = function requestId (req, res, next ) {
req.id = req.headers ['x-request-id' ] || uuidv4 ();
res.setHeader ('X-Request-ID' , req.id );
next ();
};
Request Timing Middleware :
module .exports = function timing (req, res, next ) {
const start = Date .now ();
res.on ('finish' , () => {
const duration = Date .now () - start;
console .log (`${req.method} ${req.path} - ${duration} ms` );
});
next ();
};
Authentication Middleware :
const jwt = require ('jsonwebtoken' );
const AppError = require ('../utils/AppError' );
exports .authenticate = (req, res, next ) => {
const token = req.headers .authorization ?.split (' ' )[1 ];
if (!token) {
return next (new AppError ('No token provided' , 401 ));
}
try {
const decoded = jwt.verify (token, process.env .JWT_SECRET );
req.user = decoded;
next ();
} catch (error) {
next (new AppError ('Invalid token' , 401 ));
}
};
exports .authorize = (...roles ) => {
return (req, res, next ) => {
if (!req.user ) {
return next (new AppError ('Not authenticated' , 401 ));
}
if (!roles.includes (req.user .role )) {
return next (new AppError ('Insufficient permissions' , 403 ));
}
next ();
};
};
const { authenticate, authorize } = require ('./middleware/auth' );
app.get ('/api/posts' , getPosts);
app.get ('/api/profile' , authenticate, getProfile);
app.delete ('/api/users/:id' ,
authenticate,
authorize ('admin' , 'moderator' ),
deleteUser
);
Async Middleware ✅ Correct Async Handling :
module .exports = (fn ) => {
return (req, res, next ) => {
fn (req, res, next).catch (next);
};
};
const catchAsync = require ('../utils/catchAsync' );
app.get ('/users' , catchAsync (async (req, res) => {
const users = await User .find ();
res.json ({ users });
}));
❌ Wrong: No Error Handling :
app.get ('/users' , async (req, res) => {
const users = await User .find ();
res.json ({ users });
});
Middleware Composition Compose Multiple Middleware :
const compose = (...middleware ) => {
return (req, res, next ) => {
let index = 0 ;
const dispatch = (i ) => {
if (i >= middleware.length ) return next ();
const fn = middleware[i];
try {
fn (req, res, () => dispatch (i + 1 ));
} catch (err) {
next (err);
}
};
dispatch (0 );
};
};
const adminOnly = compose (
authenticate,
authorize ('admin' ),
validateRequest
);
app.delete ('/api/users/:id' , adminOnly, deleteUser);
const conditionalMiddleware = (condition, middleware ) => {
return (req, res, next ) => {
if (condition (req)) {
return middleware (req, res, next);
}
next ();
};
};
app.use (conditionalMiddleware (
(req ) => process.env .NODE_ENV === 'development' ,
morgan ('dev' )
));
Structured Error Handling
Custom Error Classes
class AppError extends Error {
constructor (message, statusCode ) {
super (message);
this .statusCode = statusCode;
this .status = `${statusCode} ` .startsWith ('4' ) ? 'fail' : 'error' ;
this .isOperational = true ;
Error .captureStackTrace (this , this .constructor );
}
}
module .exports = AppError ;
class AppError extends Error {
constructor (message, statusCode ) {
super (message);
this .statusCode = statusCode;
this .isOperational = true ;
}
}
class ValidationError extends AppError {
constructor (message, errors = [] ) {
super (message, 400 );
this .errors = errors;
}
}
class AuthenticationError extends AppError {
constructor (message = 'Authentication required' ) {
super (message, 401 );
}
}
class AuthorizationError extends AppError {
constructor (message = 'Insufficient permissions' ) {
super (message, 403 );
}
}
class NotFoundError extends AppError {
constructor (resource = 'Resource' ) {
super (`${resource} not found` , 404 );
}
}
class ConflictError extends AppError {
constructor (message = 'Resource conflict' ) {
super (message, 409 );
}
}
module .exports = {
AppError ,
ValidationError ,
AuthenticationError ,
AuthorizationError ,
NotFoundError ,
ConflictError
};
Centralized Error Handler
const logger = require ('../config/logger' );
function errorHandler (err, req, res, next ) {
err.statusCode = err.statusCode || 500 ;
err.status = err.status || 'error' ;
logger.error ({
message : err.message ,
statusCode : err.statusCode ,
stack : err.stack ,
path : req.path ,
method : req.method ,
ip : req.ip ,
userId : req.user ?.id
});
if (process.env .NODE_ENV === 'development' ) {
return res.status (err.statusCode ).json ({
status : err.status ,
error : err,
message : err.message ,
stack : err.stack
});
}
if (err.isOperational ) {
return res.status (err.statusCode ).json ({
status : err.status ,
message : err.message ,
...(err.errors && { errors : err.errors })
});
}
console .error ('ERROR 💥' , err);
return res.status (500 ).json ({
status : 'error' ,
message : 'Something went wrong'
});
}
module .exports = errorHandler;
Handling Specific Error Types
function handleCastError (err ) {
const message = `Invalid ${err.path} : ${err.value} ` ;
return new AppError (message, 400 );
}
function handleDuplicateFields (err ) {
const field = Object .keys (err.keyValue )[0 ];
const message = `Duplicate field value: ${field} . Please use another value` ;
return new AppError (message, 400 );
}
function handleValidationError (err ) {
const errors = Object .values (err.errors ).map (el => el.message );
const message = `Invalid input data. ${errors.join('. ' )} ` ;
return new AppError (message, 400 );
}
function handleJWTError ( ) {
return new AppError ('Invalid token. Please log in again' , 401 );
}
function handleJWTExpiredError ( ) {
return new AppError ('Your token has expired. Please log in again' , 401 );
}
module .exports = (err, req, res, next ) => {
let error = { ...err };
error.message = err.message ;
if (err.name === 'CastError' ) error = handleCastError (error);
if (err.code === 11000 ) error = handleDuplicateFields (error);
if (err.name === 'ValidationError' ) error = handleValidationError (error);
if (err.name === 'JsonWebTokenError' ) error = handleJWTError ();
if (err.name === 'TokenExpiredError' ) error = handleJWTExpiredError ();
sendErrorResponse (error, req, res);
};
Async Error Handling
const catchAsync = (fn ) => {
return (req, res, next ) => {
fn (req, res, next).catch (next);
};
};
module .exports = catchAsync;
const catchAsync = require ('../utils/catchAsync' );
const User = require ('../models/User' );
const { NotFoundError } = require ('../utils/errors' );
exports .getUser = catchAsync (async (req, res, next) => {
const user = await User .findById (req.params .id );
if (!user) {
return next (new NotFoundError ('User' ));
}
res.json ({ user });
});
exports .createUser = catchAsync (async (req, res, next) => {
const user = await User .create (req.body );
res.status (201 ).json ({ user });
});
Unhandled Rejections
const app = require ('./app' );
const PORT = process.env .PORT || 3000 ;
const server = app.listen (PORT , () => {
console .log (`Server running on port ${PORT} ` );
});
process.on ('unhandledRejection' , (err ) => {
console .error ('UNHANDLED REJECTION! 💥 Shutting down...' );
console .error (err.name , err.message );
server.close (() => {
process.exit (1 );
});
});
process.on ('uncaughtException' , (err ) => {
console .error ('UNCAUGHT EXCEPTION! 💥 Shutting down...' );
console .error (err.name , err.message );
process.exit (1 );
});
process.on ('SIGTERM' , () => {
console .log ('👋 SIGTERM RECEIVED. Shutting down gracefully' );
server.close (() => {
console .log ('💥 Process terminated!' );
});
});
Security Hardening
Helmet.js Configuration
const helmet = require ('helmet' );
const securityConfig = helmet ({
contentSecurityPolicy : {
directives : {
defaultSrc : ["'self'" ],
styleSrc : ["'self'" , "'unsafe-inline'" ],
scriptSrc : ["'self'" ],
imgSrc : ["'self'" , "data:" , "https:" ],
connectSrc : ["'self'" ],
fontSrc : ["'self'" ],
objectSrc : ["'none'" ],
mediaSrc : ["'self'" ],
frameSrc : ["'none'" ],
},
},
hsts : {
maxAge : 31536000 ,
includeSubDomains : true ,
preload : true
},
frameguard : {
action : 'deny'
},
noSniff : true ,
xssFilter : true ,
referrerPolicy : {
policy : 'strict-origin-when-cross-origin'
}
});
module .exports = securityConfig;
const securityConfig = require ('./config/security' );
app.use (securityConfig);
CORS Configuration
const cors = require ('cors' );
const whitelist = process.env .ALLOWED_ORIGINS ?.split (',' ) || ['http://localhost:3000' ];
const corsOptions = {
origin : function (origin, callback ) {
if (!origin) return callback (null , true );
if (whitelist.indexOf (origin) !== -1 ) {
callback (null , true );
} else {
callback (new Error ('Not allowed by CORS' ));
}
},
credentials : true ,
methods : ['GET' , 'POST' , 'PUT' , 'DELETE' , 'PATCH' , 'OPTIONS' ],
allowedHeaders : ['Content-Type' , 'Authorization' , 'X-Requested-With' ],
exposedHeaders : ['X-Total-Count' , 'X-Page-Number' ],
maxAge : 86400
};
module .exports = cors (corsOptions);
Rate Limiting
const rateLimit = require ('express-rate-limit' );
const RedisStore = require ('rate-limit-redis' );
const redis = require ('redis' );
const redisClient = redis.createClient ({
host : process.env .REDIS_HOST ,
port : process.env .REDIS_PORT
});
exports .generalLimiter = rateLimit ({
store : new RedisStore ({
client : redisClient,
prefix : 'rl:general:'
}),
windowMs : 15 * 60 * 1000 ,
max : 100 ,
message : 'Too many requests from this IP, please try again later' ,
standardHeaders : true ,
legacyHeaders : false
});
exports .authLimiter = rateLimit ({
store : new RedisStore ({
client : redisClient,
prefix : 'rl:auth:'
}),
windowMs : 15 * 60 * 1000 ,
max : 5 ,
message : 'Too many login attempts, please try again later' ,
skipSuccessfulRequests : true
});
exports .apiKeyLimiter = rateLimit ({
windowMs : 60 * 60 * 1000 ,
max : 1000 ,
keyGenerator : (req ) => req.headers ['x-api-key' ] || req.ip ,
skip : (req ) => !req.headers ['x-api-key' ]
});
const { generalLimiter, authLimiter } = require ('./middleware/rateLimiter' );
app.use ('/api/' , generalLimiter);
app.use ('/api/auth/login' , authLimiter);
app.use ('/api/auth/register' , authLimiter);
Input Validation and Sanitization
const { body, param, query, validationResult } = require ('express-validator' );
const { ValidationError } = require ('../utils/errors' );
exports .validate = (req, res, next ) => {
const errors = validationResult (req);
if (!errors.isEmpty ()) {
const extractedErrors = errors.array ().map (err => ({
field : err.param ,
message : err.msg ,
value : err.value
}));
return next (new ValidationError ('Validation failed' , extractedErrors));
}
next ();
};
exports .createUserRules = [
body ('email' )
.isEmail ()
.normalizeEmail ()
.withMessage ('Must be a valid email' ),
body ('password' )
.isLength ({ min : 8 })
.withMessage ('Password must be at least 8 characters' )
.matches (/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/ )
.withMessage ('Password must contain uppercase, lowercase, and number' ),
body ('name' )
.trim ()
.notEmpty ()
.withMessage ('Name is required' )
.isLength ({ max : 100 })
.withMessage ('Name too long' )
.escape (),
body ('age' )
.optional ()
.isInt ({ min : 0 , max : 150 })
.withMessage ('Age must be between 0 and 150' )
];
exports .updateUserRules = [
param ('id' )
.isMongoId ()
.withMessage ('Invalid user ID' ),
body ('email' )
.optional ()
.isEmail ()
.normalizeEmail (),
body ('name' )
.optional ()
.trim ()
.notEmpty ()
.escape ()
];
const { createUserRules, validate } = require ('./middleware/validation' );
app.post ('/api/users' , createUserRules, validate, createUser);
SQL Injection Prevention
const query = `SELECT * FROM users WHERE email = '${req.body.email} '` ;
const query = 'SELECT * FROM users WHERE email = ?' ;
connection.query (query, [req.body .email ], (err, results ) => {
});
const user = await User .findOne ({ email : req.body .email });
const user = await db ('users' ).where ('email' , req.body .email ).first ();
XSS Protection
const xss = require ('xss-clean' );
app.use (xss ());
const escapeHtml = (unsafe ) => {
return unsafe
.replace (/&/g , "&" )
.replace (/</g , "<" )
.replace (/>/g , ">" )
.replace (/"/g , """ )
.replace (/'/g , "'" );
};
Environment Variable Security
require ('dotenv' ).config ();
const requiredEnvVars = [
'NODE_ENV' ,
'PORT' ,
'DATABASE_URL' ,
'JWT_SECRET' ,
'REDIS_HOST'
];
requiredEnvVars.forEach ((envVar ) => {
if (!process.env [envVar]) {
throw new Error (`Missing required environment variable: ${envVar} ` );
}
});
if (process.env .JWT_SECRET .length < 32 ) {
throw new Error ('JWT_SECRET must be at least 32 characters' );
}
module .exports = {
env : process.env .NODE_ENV ,
port : parseInt (process.env .PORT , 10 ),
database : {
url : process.env .DATABASE_URL
},
jwt : {
secret : process.env .JWT_SECRET ,
expiresIn : process.env .JWT_EXPIRES_IN || '7d'
},
redis : {
host : process.env .REDIS_HOST ,
port : parseInt (process.env .REDIS_PORT , 10 ) || 6379
}
};
Testing with Supertest
Test Setup
const mongoose = require ('mongoose' );
const { MongoMemoryServer } = require ('mongodb-memory-server' );
let mongoServer;
beforeAll (async () => {
mongoServer = await MongoMemoryServer .create ();
const mongoUri = mongoServer.getUri ();
await mongoose.connect (mongoUri);
});
afterEach (async () => {
const collections = mongoose.connection .collections ;
for (const key in collections) {
await collections[key].deleteMany ();
}
});
afterAll (async () => {
await mongoose.disconnect ();
await mongoServer.stop ();
});
Integration Testing
const request = require ('supertest' );
const app = require ('../../src/app' );
const User = require ('../../src/models/User' );
describe ('User API' , () => {
describe ('POST /api/users' , () => {
it ('should create a new user' , async () => {
const userData = {
email : 'test@example.com' ,
name : 'Test User' ,
password : 'Password123'
};
const response = await request (app)
.post ('/api/users' )
.send (userData)
.expect ('Content-Type' , /json/ )
.expect (201 );
expect (response.body ).toHaveProperty ('user' );
expect (response.body .user .email ).toBe (userData.email );
expect (response.body .user ).not .toHaveProperty ('password' );
});
it ('should return 400 for invalid email' , async () => {
const response = await request (app)
.post ('/api/users' )
.send ({
email : 'invalid-email' ,
name : 'Test User' ,
password : 'Password123'
})
.expect (400 );
expect (response.body ).toHaveProperty ('errors' );
});
it ('should return 409 for duplicate email' , async () => {
const userData = {
email : 'duplicate@example.com' ,
name : 'Test User' ,
password : 'Password123'
};
await User .create (userData);
const response = await request (app)
.post ('/api/users' )
.send (userData)
.expect (409 );
expect (response.body .message ).toMatch (/duplicate/i );
});
});
describe ('GET /api/users/:id' , () => {
it ('should get user by ID' , async () => {
const user = await User .create ({
email : 'get@example.com' ,
name : 'Get User' ,
password : 'Password123'
});
const response = await request (app)
.get (`/api/users/${user._id} ` )
.expect (200 );
expect (response.body .user ._id ).toBe (user._id .toString ());
});
it ('should return 404 for non-existent user' , async () => {
const fakeId = '507f1f77bcf86cd799439011' ;
await request (app)
.get (`/api/users/${fakeId} ` )
.expect (404 );
});
});
describe ('PUT /api/users/:id' , () => {
it ('should update user' , async () => {
const user = await User .create ({
email : 'update@example.com' ,
name : 'Update User' ,
password : 'Password123'
});
const response = await request (app)
.put (`/api/users/${user._id} ` )
.send ({ name : 'Updated Name' })
.expect (200 );
expect (response.body .user .name ).toBe ('Updated Name' );
});
});
describe ('DELETE /api/users/:id' , () => {
it ('should delete user' , async () => {
const user = await User .create ({
email : 'delete@example.com' ,
name : 'Delete User' ,
password : 'Password123'
});
await request (app)
.delete (`/api/users/${user._id} ` )
.expect (204 );
const deletedUser = await User .findById (user._id );
expect (deletedUser).toBeNull ();
});
});
});
Authentication Testing
const request = require ('supertest' );
const app = require ('../../src/app' );
const User = require ('../../src/models/User' );
describe ('Authentication' , () => {
let authToken;
let testUser;
beforeEach (async () => {
testUser = await User .create ({
email : 'auth@example.com' ,
name : 'Auth User' ,
password : 'Password123'
});
const response = await request (app)
.post ('/api/auth/login' )
.send ({
email : 'auth@example.com' ,
password : 'Password123'
});
authToken = response.body .token ;
});
describe ('POST /api/auth/login' , () => {
it ('should login with valid credentials' , async () => {
const response = await request (app)
.post ('/api/auth/login' )
.send ({
email : 'auth@example.com' ,
password : 'Password123'
})
.expect (200 );
expect (response.body ).toHaveProperty ('token' );
expect (response.body ).toHaveProperty ('user' );
});
it ('should reject invalid credentials' , async () => {
await request (app)
.post ('/api/auth/login' )
.send ({
email : 'auth@example.com' ,
password : 'WrongPassword'
})
.expect (401 );
});
});
describe ('GET /api/auth/me' , () => {
it ('should get current user with valid token' , async () => {
const response = await request (app)
.get ('/api/auth/me' )
.set ('Authorization' , `Bearer ${authToken} ` )
.expect (200 );
expect (response.body .user .email ).toBe ('auth@example.com' );
});
it ('should reject request without token' , async () => {
await request (app)
.get ('/api/auth/me' )
.expect (401 );
});
it ('should reject request with invalid token' , async () => {
await request (app)
.get ('/api/auth/me' )
.set ('Authorization' , 'Bearer invalid-token' )
.expect (401 );
});
});
});
Test Factories and Fixtures
const User = require ('../../src/models/User' );
let userCount = 0 ;
exports .createUser = async (overrides = {}) => {
userCount++;
const defaultData = {
email : `user${userCount} @example.com` ,
name : `User ${userCount} ` ,
password : 'Password123'
};
return User .create ({ ...defaultData, ...overrides });
};
exports .createUsers = async (count, overrides = {}) => {
const users = [];
for (let i = 0 ; i < count; i++) {
users.push (await exports .createUser (overrides));
}
return users;
};
const { createUser, createUsers } = require ('../factories/userFactory' );
describe ('User operations' , () => {
it ('should list all users' , async () => {
await createUsers (5 );
const response = await request (app)
.get ('/api/users' )
.expect (200 );
expect (response.body .users ).toHaveLength (5 );
});
it ('should create admin user' , async () => {
const admin = await createUser ({ role : 'admin' });
expect (admin.role ).toBe ('admin' );
});
});
Test Coverage
{
"scripts" : {
"test" : "jest" ,
"test:watch" : "jest --watch" ,
"test:coverage" : "jest --coverage" ,
"test:unit" : "jest tests/unit" ,
"test:integration" : "jest tests/integration"
},
"jest" : {
"testEnvironment" : "node" ,
"coveragePathIgnorePatterns" : ["/node_modules/" ],
"collectCoverageFrom" : [
"src/**/*.js" ,
"!src/tests/**"
],
"coverageThreshold" : {
"global" : {
"branches" : 80 ,
"functions" : 80 ,
"lines" : 80 ,
"statements" : 80
}
}
}
}
Production Operations
Environment Configuration
require ('dotenv' ).config ();
const config = {
env : process.env .NODE_ENV || 'development' ,
port : parseInt (process.env .PORT , 10 ) || 3000 ,
database : {
url : process.env .DATABASE_URL ,
poolMin : parseInt (process.env .DB_POOL_MIN , 10 ) || 2 ,
poolMax : parseInt (process.env .DB_POOL_MAX , 10 ) || 10
},
redis : {
host : process.env .REDIS_HOST || 'localhost' ,
port : parseInt (process.env .REDIS_PORT , 10 ) || 6379 ,
password : process.env .REDIS_PASSWORD
},
jwt : {
secret : process.env .JWT_SECRET ,
expiresIn : process.env .JWT_EXPIRES_IN || '7d' ,
refreshExpiresIn : process.env .JWT_REFRESH_EXPIRES_IN || '30d'
},
cors : {
origins : process.env .ALLOWED_ORIGINS ?.split (',' ) || ['http://localhost:3000' ]
},
rateLimit : {
windowMs : parseInt (process.env .RATE_LIMIT_WINDOW_MS , 10 ) || 900000 ,
max : parseInt (process.env .RATE_LIMIT_MAX , 10 ) || 100
},
logging : {
level : process.env .LOG_LEVEL || 'info' ,
file : process.env .LOG_FILE || 'logs/app.log'
}
};
const requiredConfig = [
'database.url' ,
'jwt.secret'
];
requiredConfig.forEach (key => {
const value = key.split ('.' ).reduce ((obj, k ) => obj?.[k], config);
if (!value) {
throw new Error (`Missing required configuration: ${key} ` );
}
});
module .exports = config;
NODE_ENV=production
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
DB_POOL_MIN=2
DB_POOL_MAX=10
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
JWT_SECRET=your-super-secret-jwt-key-min-32-chars
JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d
ALLOWED_ORIGINS=https://example.com,https://www.example.com
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX=100
LOG_LEVEL=info
LOG_FILE=logs/app.log
Structured Logging
const winston = require ('winston' );
const path = require ('path' );
const logLevels = {
error : 0 ,
warn : 1 ,
info : 2 ,
http : 3 ,
debug : 4
};
const logColors = {
error : 'red' ,
warn : 'yellow' ,
info : 'green' ,
http : 'magenta' ,
debug : 'blue'
};
winston.addColors (logColors);
const format = winston.format .combine (
winston.format .timestamp ({ format : 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format .errors ({ stack : true }),
winston.format .splat (),
winston.format .json ()
);
const transports = [
new winston.transports .File ({
filename : path.join ('logs' , 'error.log' ),
level : 'error' ,
maxsize : 5242880 ,
maxFiles : 5
}),
new winston.transports .File ({
filename : path.join ('logs' , 'combined.log' ),
maxsize : 5242880 ,
maxFiles : 5
})
];
if (process.env .NODE_ENV !== 'production' ) {
transports.push (
new winston.transports .Console ({
format : winston.format .combine (
winston.format .colorize ({ all : true }),
winston.format .printf (
(info ) => `${info.timestamp} ${info.level} : ${info.message} `
)
)
})
);
}
const logger = winston.createLogger ({
level : process.env .LOG_LEVEL || 'info' ,
levels : logLevels,
format,
transports
});
module .exports = logger;
const logger = require ('./config/logger' );
logger.info ('Server started' , { port : 3000 });
logger.error ('Database connection failed' , { error : err.message });
logger.debug ('User data' , { userId : user.id , email : user.email });
Request Logging Middleware :
const logger = require ('../config/logger' );
module .exports = (req, res, next ) => {
const start = Date .now ();
res.on ('finish' , () => {
const duration = Date .now () - start;
logger.http ('Request completed' , {
method : req.method ,
url : req.url ,
statusCode : res.statusCode ,
duration : `${duration} ms` ,
ip : req.ip ,
userAgent : req.get ('user-agent' ),
userId : req.user ?.id
});
});
next ();
};
Health Check Endpoints
const express = require ('express' );
const router = express.Router ();
const mongoose = require ('mongoose' );
const redis = require ('redis' );
const redisClient = redis.createClient ();
router.get ('/health' , (req, res ) => {
res.json ({
status : 'ok' ,
uptime : process.uptime (),
timestamp : new Date ().toISOString ()
});
});
router.get ('/health/detailed' , async (req, res) => {
const health = {
status : 'ok' ,
timestamp : new Date ().toISOString (),
uptime : process.uptime (),
services : {}
};
try {
const mongoState = mongoose.connection .readyState ;
health.services .mongodb = {
status : mongoState === 1 ? 'connected' : 'disconnected' ,
state : mongoState
};
} catch (error) {
health.services .mongodb = {
status : 'error' ,
error : error.message
};
health.status = 'degraded' ;
}
try {
await redisClient.ping ();
health.services .redis = {
status : 'connected'
};
} catch (error) {
health.services .redis = {
status : 'error' ,
error : error.message
};
health.status = 'degraded' ;
}
const memUsage = process.memoryUsage ();
health.memory = {
rss : `${Math .round(memUsage.rss / 1024 / 1024 )} MB` ,
heapUsed : `${Math .round(memUsage.heapUsed / 1024 / 1024 )} MB` ,
heapTotal : `${Math .round(memUsage.heapTotal / 1024 / 1024 )} MB`
};
const statusCode = health.status === 'ok' ? 200 : 503 ;
res.status (statusCode).json (health);
});
router.get ('/ready' , async (req, res) => {
try {
await mongoose.connection .db .admin ().ping ();
res.status (200 ).json ({ status : 'ready' });
} catch (error) {
res.status (503 ).json ({ status : 'not ready' , error : error.message });
}
});
router.get ('/live' , (req, res ) => {
res.status (200 ).json ({ status : 'alive' });
});
module .exports = router;
Graceful Shutdown
const app = require ('./app' );
const logger = require ('./config/logger' );
const mongoose = require ('./config/database' );
const redis = require ('./config/redis' );
const PORT = process.env .PORT || 3000 ;
const server = app.listen (PORT , () => {
logger.info (`Server running on port ${PORT} ` );
});
async function gracefulShutdown (signal ) {
logger.info (`${signal} received, starting graceful shutdown` );
server.close (async () => {
logger.info ('HTTP server closed' );
try {
await mongoose.connection .close (false );
logger.info ('MongoDB connection closed' );
await redis.quit ();
logger.info ('Redis connection closed' );
logger.info ('Graceful shutdown completed' );
process.exit (0 );
} catch (error) {
logger.error ('Error during shutdown' , { error : error.message });
process.exit (1 );
}
});
setTimeout (() => {
logger.error ('Forcing shutdown after timeout' );
process.exit (1 );
}, 30000 );
}
process.on ('SIGTERM' , () => gracefulShutdown ('SIGTERM' ));
process.on ('SIGINT' , () => gracefulShutdown ('SIGINT' ));
process.on ('uncaughtException' , (error ) => {
logger.error ('Uncaught exception' , { error : error.message , stack : error.stack });
gracefulShutdown ('uncaughtException' );
});
process.on ('unhandledRejection' , (reason, promise ) => {
logger.error ('Unhandled rejection' , { reason, promise });
gracefulShutdown ('unhandledRejection' );
});
module .exports = server;
PM2 Clustering
module .exports = {
apps : [{
name : 'express-api' ,
script : './src/server.js' ,
instances : 'max' ,
exec_mode : 'cluster' ,
env : {
NODE_ENV : 'development' ,
PORT : 3000
},
env_production : {
NODE_ENV : 'production' ,
PORT : 8080
},
autorestart : true ,
max_restarts : 10 ,
min_uptime : '10s' ,
max_memory_restart : '500M' ,
kill_timeout : 5000 ,
wait_ready : true ,
listen_timeout : 10000 ,
error_file : './logs/pm2-error.log' ,
out_file : './logs/pm2-out.log' ,
log_date_format : 'YYYY-MM-DD HH:mm:ss Z' ,
merge_logs : true ,
instance_var : 'INSTANCE_ID' ,
watch : false
}],
deploy : {
production : {
user : 'deploy' ,
host : 'production.example.com' ,
ref : 'origin/main' ,
repo : 'git@github.com:username/repo.git' ,
path : '/var/www/production' ,
'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production'
}
}
};
pm2 start ecosystem.config.js --env production
pm2 reload express-api
pm2 monit
pm2 logs express-api
pm2 scale express-api 4
pm2 stop express-api
pm2 restart express-api
pm2 delete express-api
pm2 save
pm2 startup
pm2 deploy production
Development Workflow
Nodemon Configuration {
"watch" : [ "src" ] ,
"ext" : "js,json" ,
"ignore" : [
"src/**/*.test.js" ,
"src/**/*.spec.js" ,
"node_modules/**/*" ,
"logs/**/*"
] ,
"exec" : "node src/server.js" ,
"env" : {
"NODE_ENV" : "development" ,
"PORT" : "3000"
} ,
"delay" : 1000 ,
"verbose" : false ,
"restartable" : "rs" ,
"signal" : "SIGTERM"
}
Package.json Scripts {
"scripts" : {
"dev" : "nodemon src/server.js" ,
"dev:debug" : "nodemon --inspect src/server.js" ,
"start" : "node src/server.js" ,
"test" : "jest" ,
"test:watch" : "jest --watch" ,
"test:coverage" : "jest --coverage" ,
"lint" : "eslint src/**/*.js" ,
"lint:fix" : "eslint src/**/*.js --fix" ,
"format" : "prettier --write \"src/**/*.js\"" ,
"prod" : "pm2 start ecosystem.config.js --env production" ,
"reload" : "pm2 reload express-api" ,
"stop" : "pm2 stop express-api" ,
"logs" : "pm2 logs express-api"
}
}
Decision Trees
Middleware Selection Need middleware?
├─ Security?
│ ├─ Headers → helmet
│ ├─ CORS → cors
│ ├─ Rate limiting → express-rate-limit
│ └─ Input validation → express-validator
├─ Parsing?
│ ├─ JSON → express.json()
│ ├─ Form data → express.urlencoded()
│ └─ Multipart → multer
├─ Logging?
│ ├─ Development → morgan('dev')
│ └─ Production → winston + morgan('combined')
├─ Compression?
│ └─ Response compression → compression()
└─ Authentication?
├─ Session-based → express-session + connect-redis
└─ Token-based → jsonwebtoken
Error Handling Strategy Error occurred?
├─ Operational error? (Known error)
│ ├─ Validation error → 400 with details
│ ├─ Authentication error → 401
│ ├─ Authorization error → 403
│ ├─ Not found error → 404
│ └─ Conflict error → 409
├─ Programming error? (Bug)
│ ├─ Development → Send full error + stack
│ └─ Production → Log error, send generic message
└─ External service error?
├─ Retry → Exponential backoff
└─ Circuit breaker → Fail fast
Testing Approach What to test?
├─ API endpoints?
│ └─ Integration tests → Supertest
├─ Business logic?
│ └─ Unit tests → Jest
├─ Database operations?
│ └─ Integration tests → MongoMemoryServer
├─ Authentication?
│ └─ Integration tests → Test token flow
└─ Error handling?
└─ Unit + Integration tests → Test error cases
Deployment Pattern Deployment target?
├─ Local development?
│ └─ Nodemon
├─ Single server?
│ ├─ Small app → node server.js
│ └─ Production → PM2 (single instance)
├─ Multi-core server?
│ └─ PM2 cluster mode
├─ Container?
│ ├─ Single container → Docker + node
│ └─ Orchestrated → Docker + Kubernetes
└─ Serverless?
└─ AWS Lambda + API Gateway
Common Problems & Solutions
Problem 1: Port Already in Use Error: listen EADDRINUSE: address already in use :::3000
lsof -ti:3000 | xargs kill -9
PORT=3001 npm run dev
{
"scripts" : {
"predev" : "kill-port 3000 || true" ,
"dev" : "nodemon server.js"
}
}
Problem 2: Middleware Order Issues Symptom : Routes not working, errors not caught, CORS failures
Solution : Follow correct middleware order:
Security (helmet, cors)
Rate limiting
Parsing (json, urlencoded)
Compression
Logging
Custom middleware
Routes
404 handler
Error handler (last!)
Problem 3: Unhandled Promise Rejections Symptom : UnhandledPromiseRejectionWarning
const catchAsync = require ('./utils/catchAsync' );
app.get ('/users' , catchAsync (async (req, res) => {
const users = await User .find ();
res.json ({ users });
}));
process.on ('unhandledRejection' , (err ) => {
console .error ('UNHANDLED REJECTION!' , err);
server.close (() => process.exit (1 ));
});
Problem 4: Sessions Not Working in Cluster Mode Symptom : User logged in but subsequent requests show logged out
Solution : Use Redis session store
const session = require ('express-session' );
const RedisStore = require ('connect-redis' ).default ;
const redis = require ('redis' );
const redisClient = redis.createClient ();
app.use (session ({
store : new RedisStore ({ client : redisClient }),
secret : process.env .SESSION_SECRET ,
resave : false ,
saveUninitialized : false
}));
Problem 5: Memory Leaks Symptoms : Memory usage grows over time, server crashes
pm2 start server.js --max-memory-restart 500M
node --inspect server.js
npm install -g clinic
clinic doctor -- node server.js
Anti-Patterns
❌ Don't: Mix Concerns
app.post ('/users' , async (req, res) => {
const user = new User (req.body );
user.password = await bcrypt.hash (req.body .password , 10 );
await user.save ();
const token = jwt.sign ({ id : user.id }, process.env .JWT_SECRET );
res.json ({ user, token });
});
app.post ('/users' ,
validate (createUserRules),
userController.create
);
exports .create = catchAsync (async (req, res) => {
const user = await userService.createUser (req.body );
const token = authService.generateToken (user);
res.status (201 ).json ({ user, token });
});
❌ Don't: Sync Operations
const data = fs.readFileSync ('./data.json' );
const data = await fs.promises .readFile ('./data.json' );
❌ Don't: Trust User Input
app.post ('/users' , (req, res ) => {
User .create (req.body );
});
✅ Do: Validate and Sanitize :
app.post ('/users' ,
validate (createUserRules),
userController.create
);
Quick Reference
Essential Middleware Stack const express = require ('express' );
const helmet = require ('helmet' );
const cors = require ('cors' );
const compression = require ('compression' );
const morgan = require ('morgan' );
const rateLimit = require ('express-rate-limit' );
const app = express ();
app.use (helmet ());
app.use (cors ());
app.use (rateLimit ({ windowMs : 15 * 60 * 1000 , max : 100 }));
app.use (express.json ({ limit : '10mb' }));
app.use (express.urlencoded ({ extended : true }));
app.use (compression ());
app.use (morgan ('combined' ));
app.use ('/api/v1' , require ('./routes' ));
app.use (require ('./middleware/errorHandler' ));
Essential Commands
npm run dev
npm test
npm run test :watch
npm run lint
npm start
pm2 start ecosystem.config.js
pm2 reload app
pm2 logs app
pm2 monit
npm test
npm run test :unit
npm run test :integration
npm run test :coverage
Related Skills
nodejs-backend - Node.js backend development patterns
fastify-production - Fastify framework (performance-focused alternative)
typescript-core - TypeScript with Express
docker-containerization - Containerized Express deployment
systematic-debugging - Advanced debugging techniques
Progressive Disclosure For detailed implementation guides, see:
Version : Express 4.x, PM2 5.x, Node.js 18+
Last Updated : December 2025
License : MIT