HTML & JS Topic
Node.js Middleware in Depth
Middleware Execution Flow
Understanding the Middleware Pipeline:
Middleware functions are executed in the order they are defined, forming a pipeline.
javascript
const express = require('express');
const app = express();
// Visualizing the middleware pipeline
app.use((req, res, next) => {
console.log('1. First middleware - Start');
req.startTime = Date.now();
next(); // Pass to next middleware
});
app.use((req, res, next) => {
console.log('2. Second middleware');
req.user = { id: 1, name: 'Test User' };
next();
});
app.get('/test', (req, res, next) => {
console.log('3. Route handler');
const duration = Date.now() - req.startTime;
res.json({
message: 'Request completed',
user: req.user,
duration: `${duration}ms`
});
});
app.use((req, res, next) => {
console.log('4. This runs AFTER route handler sends response');
// This won't run because response was already sent
});
app.listen(3000);
Middleware Chain Visualization:
text
Request → MW1 → MW2 → MW3 → Route Handler → Response
(next)→(next)→(next)→(res.send)→(sent to client)
Important Rules:
-
Order matters - Middleware executes in registration order
-
next() passes control to next middleware
-
res.send()/res.json()/res.end() ends the chain
-
Middleware after response is sent won't execute
Built-in Middleware
1. express.json() - Parse JSON bodies
javascript
const express = require('express');
const app = express();
// Parse application/json
app.use(express.json());
// Options
app.use(express.json({
limit: '10mb', // Maximum request body size
strict: true, // Only accept arrays and objects
inflate: true, // Handle deflated bodies
type: 'application/json', // Content-Type to parse
verify: (req, res, buf, encoding) => {
// Custom verification
try {
JSON.parse(buf);
} catch(e) {
throw new Error('Invalid JSON');
}
}
}));
// Example usage
app.post('/api/users', (req, res) => {
// req.body is now parsed JSON
console.log('Request body:', req.body);
if (!req.body.name) {
return res.status(400).json({ error: 'Name is required' });
}
res.json({ message: 'User created', data: req.body });
});
2. express.urlencoded() - Parse URL-encoded bodies
javascript
// Parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));
// Extended: false (default) uses querystring library
// Extended: true uses qs library (supports nested objects)
app.post('/form', (req, res) => {
// For form data: name=John&email=john@example.com
console.log('Form data:', req.body); // { name: 'John', email: 'john@example.com' }
res.send('Form submitted');
});
// With options
app.use(express.urlencoded({
extended: true,
limit: '10mb',
parameterLimit: 1000, // Max number of parameters
type: 'application/x-www-form-urlencoded'
}));
3. express.static() - Serve static files
javascript
const path = require('path');
// Serve static files from 'public' directory
app.use(express.static('public'));
// Multiple static directories
app.use(express.static('public'));
app.use(express.static('uploads'));
// Virtual path prefix
app.use('/static', express.static('public'));
// Now files are available at /static/css/style.css
// With options
app.use(express.static('public', {
dotfiles: 'ignore', // How to handle dotfiles: 'allow', 'deny', 'ignore'
etag: true, // Enable or disable etag generation
extensions: ['html', 'htm'], // File extensions to try
fallthrough: true, // Let requests fall through to other middleware
immutable: false, // Enable or disable immutable directive in Cache-Control
index: 'index.html', // Default file to serve
lastModified: true, // Set Last-Modified header
maxAge: 3600000, // Cache control max-age in milliseconds
redirect: true, // Redirect to trailing "/" when pathname is dir
setHeaders: (res, path, stat) => {
// Custom headers
res.set('x-timestamp', Date.now());
if (path.endsWith('.pdf')) {
res.set('Content-Disposition', 'attachment');
}
}
}));
// Example file structure:
// public/
// ├── index.html
// ├── css/
// │ └── style.css
// └── js/
// └── app.js
// Accessible at:
// GET / → serves public/index.html
// GET /css/style.css → serves public/css/style.css
4. express.Router() - Modular route handlers
javascript
// Create router instance
const router = express.Router();
// Router-level middleware
router.use((req, res, next) => {
console.log('Router middleware - Time:', Date.now());
next();
});
// Define routes
router.get('/', (req, res) => {
res.send('Router home');
});
router.get('/about', (req, res) => {
res.send('About page');
});
// Nested routers
const userRouter = express.Router();
userRouter.get('/profile', (req, res) => {
res.send('User profile');
});
router.use('/user', userRouter);
// Mount router
app.use('/api', router);
// Routes become: /api/, /api/about, /api/user/profile
5. express.text() - Parse text bodies
javascript
// Parse text/plain
app.use(express.text());
app.post('/text', (req, res) => {
console.log('Text body:', req.body); // String
res.send('Text received');
});
app.use(express.text({
type: 'text/plain', // Content-Type to parse
defaultCharset: 'utf-8' // Default charset
}));
6. express.raw() - Parse raw/buffer bodies
javascript
// Parse application/octet-stream
app.use(express.raw({
type: 'application/octet-stream',
limit: '10mb'
}));
app.post('/upload-binary', (req, res) => {
console.log('Raw buffer:', req.body); // Buffer object
// Process binary data
res.send('Binary data received');
});
Custom Middleware
1. Logger Middleware
javascript
// Custom logger middleware
const logger = (format = 'combined') => {
return (req, res, next) => {
const start = Date.now();
// Store original end method
const originalEnd = res.end;
// Override end method to log after response
res.end = function(...args) {
const duration = Date.now() - start;
const { method, url, ip } = req;
const { statusCode } = res;
const contentLength = res.get('Content-Length') || 0;
let logMessage;
switch(format) {
case 'short':
logMessage = `${method} ${url} ${statusCode} ${duration}ms`;
break;
case 'dev':
logMessage = `${method} ${url} ${statusCode} ${duration}ms - ${contentLength}`;
break;
default: // combined
const timestamp = new Date().toISOString();
const userAgent = req.get('User-Agent') || '';
logMessage = `${timestamp} ${ip} ${method} ${url} ${statusCode} ${duration}ms ${contentLength} "${userAgent}"`;
}
console.log(logMessage);
// Call original end method
originalEnd.apply(res, args);
};
next();
};
};
// Usage
app.use(logger('combined'));
app.use(logger('dev'));
app.use(logger('short'));
2. Authentication Middleware
javascript
// JWT Authentication middleware
const jwt = require('jsonwebtoken');
const authenticate = (options = {}) => {
return (req, res, next) => {
// Get token from header, query, or cookie
const token =
req.headers.authorization?.split(' ')[1] || // Bearer token
req.query.token ||
req.cookies?.token;
if (!token) {
if (options.public) {
return next(); // Allow public access
}
return res.status(401).json({ error: 'Authentication required' });
}
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Attach user to request
req.user = decoded;
// Check roles if specified
if (options.roles && !options.roles.includes(decoded.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
};
// Usage with different options
app.get('/public', authenticate({ public: true }), (req, res) => {
res.json({ message: 'Public route' });
});
app.get('/profile', authenticate(), (req, res) => {
res.json({ user: req.user });
});
app.get('/admin', authenticate({ roles: ['admin'] }), (req, res) => {
res.json({ message: 'Admin access granted' });
});
3. Validation Middleware
javascript
const { body, query, param, validationResult } = require('express-validator');
// Validation middleware factory
const validate = (validations) => {
return async (req, res, next) => {
// Run all validations
await Promise.all(validations.map(validation => validation.run(req)));
// Check for errors
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
// Format errors
const formattedErrors = errors.array().map(err => ({
field: err.param,
message: err.msg,
value: err.value,
location: err.location
}));
res.status(400).json({
error: 'Validation failed',
details: formattedErrors
});
};
};
// Usage
app.post('/register',
validate([
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 6 }),
body('confirmPassword').custom((value, { req }) => {
if (value !== req.body.password) {
throw new Error('Passwords do not match');
}
return true;
}),
body('age').optional().isInt({ min: 18, max: 100 })
]),
(req, res) => {
// If we get here, validation passed
res.json({ message: 'Registration successful' });
}
);
app.get('/users/:id',
validate([
param('id').isInt().withMessage('ID must be an integer'),
query('page').optional().isInt({ min: 1 })
]),
(req, res) => {
res.json({ userId: req.params.id });
}
);
4. Rate Limiting Middleware
javascript
const rateLimit = (options = {}) => {
const {
windowMs = 15 * 60 * 1000, // 15 minutes
max = 100, // Limit each IP to 100 requests per windowMs
message = 'Too many requests, please try again later.',
skip = () => false // Function to skip rate limiting
} = options;
const requests = new Map();
setInterval(() => {
// Clean up old entries
const now = Date.now();
for (const [ip, data] of requests.entries()) {
if (now - data.startTime > windowMs) {
requests.delete(ip);
}
}
}, windowMs);
return (req, res, next) => {
// Skip if specified
if (skip(req)) {
return next();
}
const ip = req.ip;
const now = Date.now();
if (!requests.has(ip)) {
requests.set(ip, {
count: 1,
startTime: now,
resetTime: now + windowMs
});
return next();
}
const data = requests.get(ip);
// Reset if window has passed
if (now - data.startTime > windowMs) {
data.count = 1;
data.startTime = now;
data.resetTime = now + windowMs;
return next();
}
// Check limit
if (data.count >= max) {
const retryAfter = Math.ceil((data.resetTime - now) / 1000);
res.set('Retry-After', retryAfter);
return res.status(429).json({ error: message });
}
// Increment count
data.count++;
next();
};
};
// Usage
app.use('/api/', rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 requests per minute
skip: (req) => req.ip === '127.0.0.1' // Skip for localhost
}));
// Different limits for different routes
app.use('/api/auth/', rateLimit({
windowMs: 15 * 60 * 1000,
max: 5 // 5 login attempts per 15 minutes
}));
5. Caching Middleware
javascript
const cache = (duration = 60) => { // duration in seconds
const cacheStore = new Map();
return (req, res, next) => {
// Only cache GET requests
if (req.method !== 'GET') {
return next();
}
const key = `${req.originalUrl}|${req.headers['accept-language']}`;
const cached = cacheStore.get(key);
if (cached && Date.now() < cached.expiry) {
console.log('Serving from cache');
// Set headers
res.set(cached.headers);
res.set('X-Cache', 'HIT');
return res.send(cached.body);
}
// Override res.send to cache response
const originalSend = res.send;
res.send = function(body) {
// Store in cache
if (res.statusCode === 200) {
cacheStore.set(key, {
body,
headers: {
'Content-Type': res.get('Content-Type'),
'Cache-Control': `public, max-age=${duration}`
},
expiry: Date.now() + (duration * 1000)
});
res.set('X-Cache', 'MISS');
res.set('Cache-Control', `public, max-age=${duration}`);
}
// Call original send
originalSend.call(this, body);
};
next();
};
};
// Usage
app.get('/api/products', cache(300), (req, res) => { // Cache for 5 minutes
// Expensive database query
const products = [
{ id: 1, name: 'Product 1' },
{ id: 2, name: 'Product 2' }
];
res.json(products);
});
// Clear cache manually
app.delete('/cache/clear', (req, res) => {
cacheStore.clear();
res.json({ message: 'Cache cleared' });
});
Third-party Middleware
1. CORS (Cross-Origin Resource Sharing)
javascript
const cors = require('cors');
// Basic CORS (allow all origins)
app.use(cors());
// Configure CORS
app.use(cors({
origin: [
'http://localhost:3000',
'https://example.com',
'https://*.example.com' // Wildcard subdomains
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
exposedHeaders: ['X-Total-Count', 'X-Pagination'],
credentials: true, // Allow cookies
maxAge: 86400, // Cache preflight requests (24 hours)
preflightContinue: false,
optionsSuccessStatus: 204
}));
// Route-specific CORS
app.get('/api/public', cors(), (req, res) => {
res.json({ message: 'Public API' });
});
app.get('/api/private', cors({
origin: 'https://trusted-domain.com'
}), (req, res) => {
res.json({ message: 'Private API' });
});
// Dynamic origin
app.use(cors({
origin: (origin, callback) => {
// Allow requests with no origin (like mobile apps)
if (!origin) return callback(null, true);
const allowedOrigins = ['http://localhost:3000', 'https://example.com'];
if (allowedOrigins.includes(origin)) {
return callback(null, true);
} else {
return callback(new Error('Not allowed by CORS'));
}
}
}));
2. Helmet (Security Headers)
javascript
const helmet = require('helmet');
// Use all Helmet middleware with defaults
app.use(helmet());
// Configure individual security headers
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
imgSrc: ["'self'", "data:", "https://images.example.com"],
connectSrc: ["'self'", "https://api.example.com"]
}
}));
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true
}));
// Individual middleware (more control)
app.use(helmet.frameguard({ action: 'deny' })); // Prevent clickjacking
app.use(helmet.noSniff()); // Prevent MIME type sniffing
app.use(helmet.xssFilter()); // Add XSS protection
app.use(helmet.referrerPolicy({ policy: 'same-origin' }));
app.use(helmet.permittedCrossDomainPolicies()); // Cross-domain policy
app.use(helmet.hidePoweredBy()); // Hide X-Powered-By header
// Disable specific headers for certain routes
app.use('/graphql', helmet({
contentSecurityPolicy: false // Disable CSP for GraphQL playground
}));
3. Morgan (HTTP Request Logger)
javascript
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
// Predefined formats
app.use(morgan('combined')); // Apache combined log format
app.use(morgan('common')); // Apache common log format
app.use(morgan('dev')); // Concise colored output for development
app.use(morgan('short')); // Shorter than default
app.use(morgan('tiny')); // Minimal output
// Custom format
app.use(morgan(':method :url :status :response-time ms - :res[content-length]'));
// Tokens (custom variables)
morgan.token('type', (req, res) => req.headers['content-type']);
morgan.token('date', () => new Date().toISOString());
morgan.token('user-agent', (req) => req.headers['user-agent']);
app.use(morgan(':date :method :url :status :response-time ms :user-agent'));
// Log to file
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'access.log'),
{ flags: 'a' } // Append mode
);
app.use(morgan('combined', { stream: accessLogStream }));
// Conditional logging
app.use(morgan('dev', {
skip: (req, res) => {
// Skip logging for health checks
return req.url === '/health';
}
}));
// Rotating file logs (with rotating-file-stream or winston)
4. Compression
javascript
const compression = require('compression');
// Enable compression
app.use(compression());
// With options
app.use(compression({
level: 6, // Compression level (0-9)
threshold: 1024, // Only compress responses > 1KB
filter: (req, res) => {
// Don't compress responses with this header
if (req.headers['x-no-compression']) {
return false;
}
// Use compression filter
return compression.filter(req, res);
}
}));
// Skip compression for certain routes
app.use((req, res, next) => {
if (req.url.startsWith('/api/stream')) {
// Don't compress streaming responses
next();
} else {
compression()(req, res, next);
}
});
5. Session Management (express-session)
javascript
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
// Basic session
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Redis store for production
app.use(session({
store: new RedisStore({
host: 'localhost',
port: 6379,
client: redisClient,
ttl: 86400 // 24 hours in seconds
}),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000
}
}));
// Usage
app.post('/login', (req, res) => {
// Set session data
req.session.userId = user.id;
req.session.username = user.username;
req.session.loggedIn = true;
res.json({ message: 'Logged in' });
});
app.get('/profile', (req, res) => {
if (!req.session.loggedIn) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({ user: req.session.username });
});
app.get('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Could not log out' });
}
res.clearCookie('connect.sid');
res.json({ message: 'Logged out' });
});
});
6. File Upload (Multer)
javascript
const multer = require('multer');
const path = require('path');
// Configure storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
});
// File filter
const fileFilter = (req, file, cb) => {
const allowedTypes = /jpeg|jpg|png|gif|pdf/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
} else {
cb(new Error('Only images and PDFs are allowed'));
}
};
const upload = multer({
storage,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
},
fileFilter
});
// Single file upload
app.post('/upload', upload.single('avatar'), (req, res) => {
console.log('Uploaded file:', req.file);
res.json({
message: 'File uploaded',
file: req.file
});
});
// Multiple files
app.post('/upload-multiple', upload.array('photos', 10), (req, res) => {
console.log('Uploaded files:', req.files);
res.json({
message: 'Files uploaded',
files: req.files
});
});
// Multiple fields with different files
app.post('/upload-fields', upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'gallery', maxCount: 5 }
]), (req, res) => {
console.log('Avatar:', req.files['avatar'][0]);
console.log('Gallery:', req.files['gallery']);
res.json({ message: 'Files uploaded' });
});
Error-handling Middleware
1. Basic Error Handler
javascript
// Error handling middleware MUST have 4 parameters
const errorHandler = (err, req, res, next) => {
console.error('Error:', err.stack);
// Default error properties
const status = err.status || err.statusCode || 500;
const message = err.message || 'Internal Server Error';
const code = err.code || 'INTERNAL_ERROR';
// Development vs production
const response = {
error: {
code,
message,
...(process.env.NODE_ENV === 'development' && {
stack: err.stack,
details: err.details
})
}
};
res.status(status).json(response);
};
// Register as last middleware
app.use(errorHandler);
2. Async Error Handler Wrapper
javascript
// Wrapper to catch async errors
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage
app.get('/async-route', asyncHandler(async (req, res) => {
const data = await someAsyncOperation();
if (!data) {
const error = new Error('Data not found');
error.statusCode = 404;
throw error;
}
res.json(data);
}));
// For routes with middleware
const validate = asyncHandler(async (req, res, next) => {
// Async validation logic
const isValid = await validateToken(req.headers.authorization);
if (!isValid) {
throw new Error('Invalid token');
}
next();
});
app.get('/protected', validate, (req, res) => {
res.json({ message: 'Protected route' });
});
3. Custom Error Classes
javascript
// Base error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true; // Operational errors (not programming errors)
Error.captureStackTrace(this, this.constructor);
}
}
// Specific error classes
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed', 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);
}
}
// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
if (user.isPrivate && !req.user.isAdmin) {
throw new AuthorizationError();
}
res.json(user);
}));
// Updated error handler
app.use((err, req, res, next) => {
// Log error
console.error(err);
// Set defaults for non-AppError instances
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
// Handle operational vs programming errors
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message,
...(err.errors && { errors: err.errors })
});
} else {
// Programming or unknown errors
console.error('UNEXPECTED ERROR 💥', err);
res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
}
});
4. 404 Handler
javascript
// Catch-all route for 404
app.use('*', (req, res, next) => {
// For API routes
if (req.originalUrl.startsWith('/api')) {
return next(new NotFoundError(`Cannot ${req.method} ${req.originalUrl}`));
}
// For web routes
res.status(404).render('404', {
title: 'Page Not Found',
url: req.url
});
});
// Alternative: Create 404 middleware
const notFoundHandler = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`);
error.statusCode = 404;
next(error); // Pass to error handler
};
app.use(notFoundHandler);
5. Global Error Handler with Different Environments
javascript
// Development error handler (detailed)
const sendErrorDev = (err, res) => {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
};
// Production error handler (limited info)
const sendErrorProd = (err, res) => {
// Operational, trusted error: send message to client
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
// Programming or unknown error: don't leak error details
else {
// 1) Log error
console.error('ERROR ', err);
// 2) Send generic message
res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
}
};
// Global error handling middleware
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
sendErrorDev(err, res);
} else {
// Handle invalid database IDs, duplicate keys, etc.
let error = { ...err };
error.message = err.message;
// Mongoose bad ObjectId
if (err.name === 'CastError') {
error = new AppError(`Invalid ${err.path}: ${err.value}`, 400);
}
// Mongoose duplicate key
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
error = new AppError(`Duplicate field value: ${field}`, 400);
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(el => el.message);
error = new AppError(`Invalid input data: ${errors.join('. ')}`, 400);
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
error = new AppError('Invalid token', 401);
}
if (err.name === 'TokenExpiredError') {
error = new AppError('Token expired', 401);
}
sendErrorProd(error, res);
}
});
Complete Middleware Stack Example:
javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const app = express();
// 1. Security middleware
app.use(helmet());
// 2. CORS
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
}));
// 3. Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // Limit each IP to 100 requests per window
});
app.use('/api/', limiter);
// 4. Request logging
app.use(morgan(process.env.NODE_ENV === 'development' ? 'dev' : 'combined'));
// 5. Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// 6. Compression
app.use(compression());
// 7. Custom middleware
app.use((req, res, next) => {
// Add request ID
req.id = Date.now().toString(36) + Math.random().toString(36).substr(2);
// Set start time for performance tracking
req.startTime = Date.now();
next();
});
// 8. Routes
app.use('/api/v1/users', require('./routes/users'));
app.use('/api/v1/auth', require('./routes/auth'));
// 9. 404 handler
app.use('*', (req, res) => {
res.status(404).json({
status: 'fail',
message: `Cannot ${req.method} ${req.originalUrl}`
});
});
// 10. Global error handler
app.use((err, req, res, next) => {
console.error(`[${req.id}] Error:`, err);
const statusCode = err.statusCode || 500;
const message = err.message || 'Internal Server Error';
res.status(statusCode).json({
status: 'error',
message,
requestId: req.id,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
});
This extensive guide covers middleware from basic to advanced concepts, showing how to create, use, and combine middleware to build robust Express applications.
Comments (0)
No comments yet. Be the first to comment!