| name | express-nodejs-expert |
| description | Expert knowledge of Express.js and Node.js for building production-ready web applications and APIs. Covers middleware patterns, routing, async/await error handling, security, performance optimization, proxy patterns, static file serving, and production deployment. Use when working with server.js, adding routes, implementing middleware, debugging Express issues, or optimizing API endpoints. |
Express.js & Node.js Expert
This skill provides comprehensive expert knowledge of Express.js web framework and Node.js runtime for building robust, secure, and performant web applications and API servers.
Express Application Structure
Basic Application Setup
Minimal Express app:
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
Production-ready structure:
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const app = express();
const port = process.env.PORT || 3000;
// Middleware
app.use(helmet()); // Security headers
app.use(cors()); // CORS support
app.use(morgan('combined')); // Logging
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
app.use(express.static('public')); // Serve static files
// Routes
app.use('/api', require('./routes/api'));
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: {
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
// Start server
const server = app.listen(port, () => {
console.log(`Server running in ${process.env.NODE_ENV || 'development'} mode on port ${port}`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
server.close(() => {
console.log('HTTP server closed');
});
});
module.exports = app;
Middleware Patterns
Middleware Execution Order
Order matters:
// 1. Security middleware (first)
app.use(helmet());
// 2. Logging
app.use(morgan('combined'));
// 3. Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 4. CORS
app.use(cors());
// 5. Static files
app.use(express.static('public'));
// 6. Custom middleware
app.use(customMiddleware);
// 7. Routes
app.use('/api', apiRoutes);
// 8. 404 handler (after all routes)
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
// 9. Error handler (last)
app.use((err, req, res, next) => {
// Error handling
});
Built-in Middleware
express.json() - Parse JSON request bodies:
app.use(express.json({ limit: '10mb' })); // Set size limit
// Now req.body contains parsed JSON
app.post('/api/data', (req, res) => {
console.log(req.body); // { key: 'value' }
res.json({ received: req.body });
});
express.urlencoded() - Parse URL-encoded bodies:
app.use(express.urlencoded({ extended: true }));
// Handles form submissions
app.post('/form', (req, res) => {
console.log(req.body); // { name: 'John', email: 'john@example.com' }
});
express.static() - Serve static files:
// Serve files from 'public' directory
app.use(express.static('public'));
// With options
app.use(express.static('public', {
maxAge: '1d', // Cache for 1 day
etag: true,
lastModified: true,
index: 'index.html'
}));
// Multiple static directories
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));
Custom Middleware
Simple middleware:
// Logging middleware
const logger = (req, res, next) => {
console.log(`${req.method} ${req.path}`);
next(); // MUST call next() to continue
};
app.use(logger);
Async middleware:
const asyncMiddleware = async (req, res, next) => {
try {
// Async operations
const data = await fetchData();
req.data = data;
next();
} catch (error) {
next(error); // Pass errors to error handler
}
};
app.use(asyncMiddleware);
Conditional middleware:
const devOnly = (req, res, next) => {
if (process.env.NODE_ENV === 'development') {
return next();
}
res.status(403).json({ error: 'Development only' });
};
app.get('/debug', devOnly, (req, res) => {
res.json({ debug: 'info' });
});
Error handling middleware (must have 4 parameters):
app.use((err, req, res, next) => {
console.error(err.stack);
// Handle specific error types
if (err.name === 'ValidationError') {
return res.status(400).json({ error: err.message });
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({ error: 'Unauthorized' });
}
// Generic error
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message
});
});
Routing Best Practices
Route Organization
Inline routes (small apps):
app.get('/', (req, res) => res.send('Home'));
app.get('/about', (req, res) => res.send('About'));
app.post('/api/users', (req, res) => {/* ... */});
Router modules (recommended):
// routes/api.js
const express = require('express');
const router = express.Router();
router.get('/users', (req, res) => {
res.json({ users: [] });
});
router.post('/users', (req, res) => {
res.json({ created: true });
});
module.exports = router;
// server.js
const apiRoutes = require('./routes/api');
app.use('/api', apiRoutes);
Grouped routes by resource:
// routes/users.js
const router = express.Router();
router.get('/', getAllUsers);
router.get('/:id', getUser);
router.post('/', createUser);
router.put('/:id', updateUser);
router.delete('/:id', deleteUser);
module.exports = router;
// server.js
app.use('/api/users', require('./routes/users'));
Route Parameters
Path parameters:
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ userId });
});
// Multiple parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
Query parameters:
app.get('/search', (req, res) => {
const { q, page, limit } = req.query;
// /search?q=test&page=2&limit=10
res.json({ query: q, page, limit });
});
Route parameter validation:
// Middleware to validate ID
const validateId = (req, res, next) => {
const id = parseInt(req.params.id);
if (isNaN(id) || id < 1) {
return res.status(400).json({ error: 'Invalid ID' });
}
req.params.id = id; // Convert to number
next();
};
app.get('/users/:id', validateId, (req, res) => {
// req.params.id is now a number
});
HTTP Methods
RESTful API routes:
const router = express.Router();
// GET - Retrieve resources
router.get('/items', (req, res) => {
res.json({ items: [] });
});
router.get('/items/:id', (req, res) => {
res.json({ item: {} });
});
// POST - Create resource
router.post('/items', (req, res) => {
const newItem = req.body;
res.status(201).json({ created: newItem });
});
// PUT - Update entire resource
router.put('/items/:id', (req, res) => {
const updated = req.body;
res.json({ updated });
});
// PATCH - Partial update
router.patch('/items/:id', (req, res) => {
const updates = req.body;
res.json({ updated: updates });
});
// DELETE - Remove resource
router.delete('/items/:id', (req, res) => {
res.status(204).send(); // No content
});
Async/Await Error Handling
The Problem
Without proper handling:
// BAD - Unhandled promise rejection
app.get('/users', async (req, res) => {
const users = await fetchUsers(); // If this throws, app crashes
res.json(users);
});
Solutions
Option 1: Try-catch in every route:
app.get('/users', async (req, res) => {
try {
const users = await fetchUsers();
res.json(users);
} catch (error) {
console.error(error);
res.status(500).json({ error: error.message });
}
});
Option 2: Async wrapper utility (recommended):
// utils/asyncHandler.js
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;
// Usage
const asyncHandler = require('./utils/asyncHandler');
app.get('/users', asyncHandler(async (req, res) => {
const users = await fetchUsers(); // Errors automatically caught
res.json(users);
}));
Option 3: express-async-errors package:
// Install: npm install express-async-errors
require('express-async-errors'); // At the top of your app
// Now async errors are automatically caught
app.get('/users', async (req, res) => {
const users = await fetchUsers();
res.json(users);
});
// Error handler catches async errors
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
Custom Error Classes
// errors/AppError.js
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;
// Usage
const AppError = require('./errors/AppError');
app.get('/users/:id', async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError('User not found', 404));
}
res.json(user);
});
// Error handler
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
res.status(err.statusCode).json({
status: err.status,
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
Request/Response Patterns
Request Object
Common properties:
app.post('/api/data', (req, res) => {
// URL parameters
const { id } = req.params; // /api/data/:id
// Query string
const { page, limit } = req.query; // /api/data?page=1&limit=10
// Request body (requires express.json())
const data = req.body;
// Headers
const contentType = req.get('Content-Type');
const auth = req.headers.authorization;
// Request info
const method = req.method; // POST
const path = req.path; // /api/data
const url = req.url; // /api/data?page=1
const protocol = req.protocol; // http or https
const ip = req.ip; // Client IP
// Cookies (requires cookie-parser)
const sessionId = req.cookies.sessionId;
});
Response Methods
Send responses:
// Send JSON
res.json({ message: 'Success' });
res.status(201).json({ created: true });
// Send text
res.send('Plain text');
// Send HTML
res.send('<h1>HTML</h1>');
// Send file
res.sendFile('/path/to/file.pdf');
// Download file
res.download('/path/to/file.pdf', 'filename.pdf');
// Redirect
res.redirect('/new-url');
res.redirect(301, '/permanent-redirect');
// Set status
res.status(404).json({ error: 'Not Found' });
res.sendStatus(204); // No Content
Set headers:
res.set('Content-Type', 'application/json');
res.set({
'Content-Type': 'application/json',
'X-Custom-Header': 'value'
});
// Set cookies
res.cookie('sessionId', '12345', {
maxAge: 900000,
httpOnly: true,
secure: true,
sameSite: 'strict'
});
// Clear cookies
res.clearCookie('sessionId');
Response Patterns
Success responses:
// 200 OK - General success
res.json({ data: items });
// 201 Created - Resource created
res.status(201).json({ id: newId, created: true });
// 204 No Content - Success with no response body
res.status(204).send();
Error responses:
// 400 Bad Request - Invalid input
res.status(400).json({ error: 'Invalid email format' });
// 401 Unauthorized - Authentication required
res.status(401).json({ error: 'Authentication required' });
// 403 Forbidden - Insufficient permissions
res.status(403).json({ error: 'Access denied' });
// 404 Not Found - Resource doesn't exist
res.status(404).json({ error: 'User not found' });
// 409 Conflict - Resource conflict
res.status(409).json({ error: 'Email already exists' });
// 422 Unprocessable Entity - Validation error
res.status(422).json({
error: 'Validation failed',
details: validationErrors
});
// 500 Internal Server Error - Server error
res.status(500).json({ error: 'Internal server error' });
Proxy Patterns with Axios
Basic Proxy
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
app.post('/api/proxy', async (req, res) => {
try {
const response = await axios.post(
'https://external-api.com/endpoint',
req.body,
{ headers: { 'Content-Type': 'application/json' } }
);
res.json(response.data);
} catch (error) {
console.error('Proxy error:', error.message);
res.status(error.response?.status || 500).json({
error: 'Proxy request failed',
details: error.response?.data || error.message
});
}
});
Advanced Proxy with Headers
app.post('/api/proxy', async (req, res) => {
try {
// Forward headers from client
const headers = {
'Content-Type': 'application/json',
'Authorization': req.headers.authorization,
'User-Agent': req.headers['user-agent']
};
const response = await axios.post(
'https://external-api.com/endpoint',
req.body,
{
headers,
timeout: 5000, // 5 second timeout
validateStatus: (status) => status < 500 // Don't throw on 4xx
}
);
// Forward response headers
res.set('X-Response-Time', response.headers['x-response-time']);
res.status(response.status).json(response.data);
} catch (error) {
if (error.code === 'ECONNABORTED') {
return res.status(504).json({ error: 'Gateway timeout' });
}
res.status(error.response?.status || 500).json({
error: 'Proxy request failed',
originalError: error.response?.data || null
});
}
});
Proxy with Retry Logic
const axios = require('axios');
const axiosRetry = require('axios-retry');
// Configure axios with retry
axiosRetry(axios, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error)
|| error.response?.status === 429; // Retry on rate limit
}
});
app.post('/api/proxy', async (req, res) => {
try {
const response = await axios.post(
'https://external-api.com/endpoint',
req.body,
{
headers: { 'Content-Type': 'application/json' },
'axios-retry': {
retries: 3
}
}
);
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Request failed after retries',
details: error.response?.data
});
}
});
Request Validation Before Proxying
const Joi = require('joi');
const requestSchema = Joi.object({
keyword: Joi.string().max(100),
startDate: Joi.date().iso(),
endDate: Joi.date().iso().min(Joi.ref('startDate'))
});
app.post('/api/search', async (req, res) => {
// Validate request body
const { error, value } = requestSchema.validate(req.body);
if (error) {
return res.status(400).json({
error: 'Validation failed',
details: error.details
});
}
try {
const response = await axios.post(
'https://external-api.com/search',
value, // Use validated data
{ headers: { 'Content-Type': 'application/json' } }
);
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Search request failed',
details: error.response?.data
});
}
});
Security Best Practices
Helmet - Security Headers
const helmet = require('helmet');
// Basic usage
app.use(helmet());
// Custom configuration
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
})
);
CORS Configuration
const cors = require('cors');
// Allow all origins (development only)
app.use(cors());
// Production configuration
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'https://myapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // 24 hours
}));
// Dynamic origin validation
app.use(cors({
origin: (origin, callback) => {
const allowedOrigins = ['https://myapp.com', 'https://app.example.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));
Rate Limiting
const rateLimit = require('express-rate-limit');
// Global rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later.',
standardHeaders: true,
legacyHeaders: false
});
app.use(limiter);
// Route-specific rate limit
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts, please try again later.'
});
app.post('/api/login', strictLimiter, (req, res) => {
// Login logic
});
Input Validation and Sanitization
const { body, validationResult } = require('express-validator');
app.post('/api/users',
// Validation middleware
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).trim(),
body('name').trim().escape(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Proceed with validated data
const { email, password, name } = req.body;
res.json({ created: true });
}
);
Prevent Parameter Pollution
const hpp = require('hpp');
// Prevent query parameter pollution
app.use(hpp());
// Whitelist certain parameters that can be arrays
app.use(hpp({
whitelist: ['tags', 'categories']
}));
Performance Optimization
Compression
const compression = require('compression');
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6 // Compression level (0-9)
}));
Caching Headers
// Static files with caching
app.use(express.static('public', {
maxAge: '1d',
etag: true,
lastModified: true
}));
// API responses with cache control
app.get('/api/data', (req, res) => {
res.set('Cache-Control', 'public, max-age=300'); // 5 minutes
res.json({ data: [] });
});
// No cache for dynamic data
app.get('/api/user/profile', (req, res) => {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
res.json({ user: {} });
});
Response Time Tracking
const responseTime = require('response-time');
app.use(responseTime((req, res, time) => {
console.log(`${req.method} ${req.url} - ${time.toFixed(2)}ms`);
}));
// Or send as header
app.use(responseTime());
Connection Pooling (for databases)
// Example with PostgreSQL
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 20, // Maximum pool size
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
app.get('/api/users', async (req, res) => {
const client = await pool.connect();
try {
const result = await client.query('SELECT * FROM users');
res.json(result.rows);
} finally {
client.release();
}
});
Production Configuration
Environment-based Configuration
const express = require('express');
const app = express();
const isDevelopment = process.env.NODE_ENV === 'development';
const isProduction = process.env.NODE_ENV === 'production';
// Development-only middleware
if (isDevelopment) {
const morgan = require('morgan');
app.use(morgan('dev'));
}
// Production-only middleware
if (isProduction) {
app.use(require('compression')());
app.use(require('helmet')());
}
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: isProduction ? 'Internal Server Error' : err.message,
...(isDevelopment && { stack: err.stack })
});
});
Graceful Shutdown
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const server = app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
// Handle shutdown signals
const gracefulShutdown = (signal) => {
console.log(`\n${signal} signal received: closing HTTP server`);
server.close(() => {
console.log('HTTP server closed');
// Close database connections, cleanup resources
// db.close();
process.exit(0);
});
// Force close after 10 seconds
setTimeout(() => {
console.error('Forcing shutdown');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
gracefulShutdown('uncaughtException');
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
gracefulShutdown('unhandledRejection');
});
Logging
const winston = require('winston');
const morgan = require('morgan');
// Winston logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// Morgan for HTTP logging
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim())
}
}));
// Use logger in routes
app.get('/api/data', async (req, res) => {
try {
const data = await fetchData();
logger.info('Data fetched successfully');
res.json(data);
} catch (error) {
logger.error('Error fetching data:', { error: error.message, stack: error.stack });
res.status(500).json({ error: 'Internal server error' });
}
});
Testing Express Applications
Setup with Jest and Supertest
// Install: npm install --save-dev jest supertest
// server.test.js
const request = require('supertest');
const app = require('./server');
describe('GET /', () => {
it('responds with 200 status', async () => {
const response = await request(app).get('/');
expect(response.status).toBe(200);
});
it('responds with JSON', async () => {
const response = await request(app).get('/api/users');
expect(response.headers['content-type']).toMatch(/json/);
});
});
describe('POST /api/users', () => {
it('creates a user with valid data', async () => {
const userData = { name: 'John', email: 'john@example.com' };
const response = await request(app)
.post('/api/users')
.send(userData)
.set('Content-Type', 'application/json');
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('id');
});
it('rejects invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'invalid' });
expect(response.status).toBe(400);
});
});
Mocking External APIs
const axios = require('axios');
jest.mock('axios');
describe('POST /api/proxy', () => {
it('proxies request successfully', async () => {
const mockData = { result: 'success' };
axios.post.mockResolvedValue({ data: mockData });
const response = await request(app)
.post('/api/proxy')
.send({ query: 'test' });
expect(response.status).toBe(200);
expect(response.body).toEqual(mockData);
expect(axios.post).toHaveBeenCalledWith(
expect.any(String),
{ query: 'test' },
expect.any(Object)
);
});
it('handles proxy errors', async () => {
axios.post.mockRejectedValue(new Error('Network error'));
const response = await request(app)
.post('/api/proxy')
.send({ query: 'test' });
expect(response.status).toBe(500);
expect(response.body).toHaveProperty('error');
});
});
Common Express Issues
Headers Already Sent
Problem:
// BAD - sends headers twice
app.get('/data', (req, res) => {
res.json({ data: [] });
res.status(200).send(); // Error: headers already sent
});
Solution:
// GOOD - single response
app.get('/data', (req, res) => {
return res.json({ data: [] }); // Use return to prevent further execution
});
// Or use else
app.get('/data', (req, res) => {
if (error) {
return res.status(500).json({ error: 'Failed' });
} else {
return res.json({ data: [] });
}
});
Middleware Not Running
Problem: Middleware defined after routes
// BAD - middleware defined after route
app.get('/api/data', (req, res) => {
res.json({ data: req.user }); // req.user is undefined
});
app.use(authMiddleware); // Too late!
Solution: Define middleware before routes
// GOOD - middleware before routes
app.use(authMiddleware);
app.get('/api/data', (req, res) => {
res.json({ data: req.user }); // req.user exists
});
Forgot to call next()
Problem:
// BAD - middleware doesn't call next()
app.use((req, res) => {
console.log('Request received');
// Request hangs here!
});
Solution:
// GOOD - always call next()
app.use((req, res, next) => {
console.log('Request received');
next(); // Continue to next middleware
});
CORS Errors
Problem: Missing CORS headers
// Frontend gets CORS error
Solution:
const cors = require('cors');
app.use(cors());
// Or manual headers
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
Body Parser Not Working
Problem: req.body is undefined
app.post('/api/data', (req, res) => {
console.log(req.body); // undefined
});
Solution: Add body parser middleware
app.use(express.json()); // Parse JSON
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded
app.post('/api/data', (req, res) => {
console.log(req.body); // Now it works
});
Best Practices Summary
Application Structure
- Use environment variables for configuration
- Implement graceful shutdown
- Use middleware in correct order
- Separate routes into modules
- Implement proper error handling
Security
- Always use helmet for security headers
- Configure CORS appropriately
- Implement rate limiting
- Validate and sanitize all inputs
- Never expose sensitive errors in production
- Use HTTPS in production
- Keep dependencies updated
Performance
- Use compression middleware
- Implement caching where appropriate
- Use connection pooling for databases
- Minimize middleware stack
- Use async/await instead of callbacks
Error Handling
- Use async error wrapper or express-async-errors
- Create custom error classes
- Centralize error handling middleware
- Log errors appropriately
- Never crash on unhandled errors
Code Quality
- Use consistent naming conventions
- Keep route handlers small and focused
- Extract business logic into separate modules
- Write tests for routes and middleware
- Use TypeScript for larger applications
Production Readiness
- Set NODE_ENV=production
- Implement logging (Winston, Morgan)
- Use process managers (PM2, Docker)
- Monitor application health
- Implement graceful shutdown
- Handle uncaught exceptions
Resources
- Express.js Documentation: https://expressjs.com/
- Node.js Best Practices: https://github.com/goldbergyoni/nodebestpractices
- Express Security: https://expressjs.com/en/advanced/best-practice-security.html
- Helmet Documentation: https://helmetjs.github.io/
- Axios Documentation: https://axios-http.com/docs/intro