Express.js Development Skill
This skill provides comprehensive guidance for building production-ready web applications and REST APIs using Express.js, covering routing, middleware, request/response handling, error handling, authentication, validation, and deployment best practices.
When to Use This Skill
Use this skill when:
-
Building RESTful APIs for web and mobile applications
-
Creating backend services and microservices
-
Developing web servers with server-side rendering
-
Implementing API gateways and proxy servers
-
Building real-time applications with WebSocket support
-
Creating middleware-based request processing pipelines
-
Developing authentication and authorization systems
-
Implementing file upload and download services
-
Building webhook handlers and integrations
-
Creating serverless functions with Express
Core Concepts
Application Setup
Express applications are built by creating an instance of Express and configuring middleware and routes.
Basic Express Application:
const express = require('express'); const app = express();
// Middleware app.use(express.json()); app.use(express.urlencoded({ extended: true }));
// Routes app.get('/', (req, res) => { res.send('Hello World!'); });
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(Server running on port ${PORT});
});
Application with Configuration:
const express = require('express'); const app = express();
// App settings app.set('port', process.env.PORT || 3000); app.set('env', process.env.NODE_ENV || 'development'); app.set('trust proxy', 1); // Trust first proxy
// View engine setup (optional) app.set('view engine', 'ejs'); app.set('views', './views');
// Static files app.use(express.static('public'));
// Body parsing app.use(express.json()); app.use(express.urlencoded({ extended: true }));
module.exports = app;
Routing
Routing refers to how an application's endpoints (URIs) respond to client requests.
Basic Routes:
const express = require('express'); const app = express();
// HTTP Methods app.get('/users', (req, res) => { res.json({ message: 'Get all users' }); });
app.post('/users', (req, res) => { res.json({ message: 'Create user' }); });
app.put('/users/:id', (req, res) => {
res.json({ message: Update user ${req.params.id} });
});
app.delete('/users/:id', (req, res) => {
res.json({ message: Delete user ${req.params.id} });
});
// Multiple methods on same route app.route('/users/:id') .get((req, res) => res.json({ message: 'Get user' })) .put((req, res) => res.json({ message: 'Update user' })) .delete((req, res) => res.json({ message: 'Delete user' }));
Route Parameters:
// Single parameter app.get('/users/:userId', (req, res) => { const { userId } = req.params; res.json({ userId }); });
// Multiple parameters app.get('/users/:userId/posts/:postId', (req, res) => { const { userId, postId } = req.params; res.json({ userId, postId }); });
// Optional parameters with regex app.get('/users/:userId/posts/:postId?', (req, res) => { // postId is optional res.json(req.params); });
// Parameter validation app.param('userId', (req, res, next, id) => { // Validate or transform parameter if (!id.match(/^\d+$/)) { return res.status(400).json({ error: 'Invalid user ID' }); } req.userId = parseInt(id); next(); });
Query Strings:
// GET /search?q=express&limit=10&page=2 app.get('/search', (req, res) => { const { q, limit = 20, page = 1 } = req.query; res.json({ query: q, limit: parseInt(limit), page: parseInt(page) }); });
Router Modules:
// routes/users.js const express = require('express'); const router = express.Router();
router.get('/', (req, res) => { res.json({ message: 'Get all users' }); });
router.get('/:id', (req, res) => {
res.json({ message: Get user ${req.params.id} });
});
router.post('/', (req, res) => { res.json({ message: 'Create user' }); });
module.exports = router;
// app.js const usersRouter = require('./routes/users'); app.use('/api/users', usersRouter);
Middleware
Middleware functions have access to the request object (req), the response object (res), and the next middleware function in the application's request-response cycle.
Application-Level Middleware:
// Executed for every request
app.use((req, res, next) => {
console.log(${req.method} ${req.path});
next();
});
// Executed for specific path app.use('/api', (req, res, next) => { req.startTime = Date.now(); next(); });
// Multiple middleware functions app.use( express.json(), express.urlencoded({ extended: true }), cookieParser() );
Router-Level Middleware:
const router = express.Router();
// Middleware for all routes in this router router.use((req, res, next) => { console.log('Router middleware'); next(); });
// Middleware for specific route router.get('/users', authMiddleware, validationMiddleware, (req, res) => { res.json({ users: [] }); } );
Built-in Middleware:
// Parse JSON bodies app.use(express.json());
// Parse URL-encoded bodies app.use(express.urlencoded({ extended: true }));
// Serve static files app.use(express.static('public')); app.use('/uploads', express.static('uploads'));
Third-Party Middleware:
const cors = require('cors'); const helmet = require('helmet'); const morgan = require('morgan'); const compression = require('compression');
// Security headers app.use(helmet());
// CORS app.use(cors({ origin: 'https://example.com', credentials: true }));
// Logging app.use(morgan('combined'));
// Compression app.use(compression());
Custom Middleware:
// Request logging middleware function requestLogger(req, res, next) { const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(${req.method} ${req.path} ${res.statusCode} ${duration}ms);
});
next(); }
// Authentication middleware function requireAuth(req, res, next) { const token = req.headers.authorization?.split(' ')[1];
if (!token) { return res.status(401).json({ error: 'No token provided' }); }
try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); } }
// Request validation middleware function validateUser(req, res, next) { const { email, password } = req.body;
if (!email || !password) { return res.status(400).json({ error: 'Email and password are required' }); }
if (!email.includes('@')) { return res.status(400).json({ error: 'Invalid email' }); }
next(); }
app.use(requestLogger); app.post('/login', validateUser, loginHandler); app.get('/protected', requireAuth, protectedHandler);
Request Object
The request object represents the HTTP request and has properties for query strings, parameters, body, headers, etc.
Request Properties:
app.post('/api/users/:id', (req, res) => { // Route parameters const { id } = req.params;
// Query string const { sort, filter } = req.query;
// Request body const { name, email } = req.body;
// Headers const userAgent = req.get('User-Agent'); const contentType = req.get('Content-Type');
// Request info const method = req.method; const path = req.path; const url = req.url; const baseUrl = req.baseUrl; const protocol = req.protocol; const hostname = req.hostname; const ip = req.ip;
// Cookies (requires cookie-parser) const { sessionId } = req.cookies;
res.json({ id, name, email }); });
Request Methods:
app.post('/upload', (req, res) => { // Check content type if (req.is('application/json')) { // Handle JSON }
// Check accept header if (req.accepts('json')) { res.json({ data: 'json response' }); } else if (req.accepts('html')) { res.send('<html>html response</html>'); }
// Get header value const auth = req.get('Authorization');
// Get range header const range = req.range(1000); });
Response Object
The response object represents the HTTP response that an Express app sends when it gets an HTTP request.
Sending Responses:
app.get('/api/data', (req, res) => { // Send JSON res.json({ message: 'Success', data: [] });
// Send string res.send('Hello World');
// Send status res.sendStatus(200); // Equivalent to res.status(200).send('OK')
// Send file res.sendFile('/path/to/file.pdf');
// Download file res.download('/path/to/file.pdf', 'document.pdf');
// Render view res.render('index', { title: 'Home' });
// Redirect res.redirect('/login'); res.redirect(301, 'https://example.com');
// End response res.end(); });
Setting Status and Headers:
app.get('/api/resource', (req, res) => { // Set status code res.status(201).json({ created: true });
// Set headers res.set('Content-Type', 'application/json'); res.set({ 'X-API-Version': '1.0', 'X-Rate-Limit': '100' });
// Set cookie res.cookie('name', 'value', { maxAge: 900000, httpOnly: true, secure: true, sameSite: 'strict' });
// Clear cookie res.clearCookie('name');
res.json({ success: true }); });
Response Formats:
app.get('/api/users/:id', (req, res) => { const user = { id: 1, name: 'John' };
res.format({
'text/plain': () => {
res.send(${user.name});
},
'text/html': () => {
res.send(<p>${user.name}</p>);
},
'application/json': () => {
res.json(user);
},
default: () => {
res.status(406).send('Not Acceptable');
}
});
});
Error Handling
Error-handling middleware functions have four arguments: (err, req, res, next).
Error-Handling Middleware:
// 404 handler app.use((req, res, next) => { res.status(404).json({ error: 'Not found' }); });
// Error handler (must be last) 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 }) } }); });
Async Error Handling:
// Async wrapper utility const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); };
// Using async wrapper app.get('/api/users/:id', asyncHandler(async (req, res) => { const user = await User.findById(req.params.id);
if (!user) { const error = new Error('User not found'); error.status = 404; throw error; }
res.json(user); }));
// Custom error classes class AppError extends Error { constructor(message, status) { super(message); this.status = status; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } }
class NotFoundError extends AppError { constructor(message = 'Resource not found') { super(message, 404); } }
class ValidationError extends AppError { constructor(message = 'Validation failed') { super(message, 400); } }
API Reference
Express Application Methods
app.use([path], middleware)
-
Mounts middleware at the specified path
-
If path is not specified, middleware is executed for every request
app.METHOD(path, [middleware...], handler)
-
Routes HTTP requests (GET, POST, PUT, DELETE, etc.)
-
Multiple middleware functions can be specified
app.route(path)
- Returns an instance of a single route for chaining HTTP verbs
app.listen(port, [hostname], [backlog], [callback])
- Binds and listens for connections on the specified host and port
app.param(name, callback)
- Adds callback triggers to route parameters
app.set(name, value)
- Assigns setting name to value
app.get(name)
- Returns the value of setting name
Router Methods
router.use([path], middleware)
- Mounts middleware for the router
router.METHOD(path, [middleware...], handler)
- Routes HTTP requests within the router
router.route(path)
- Returns a route instance for chaining
router.param(name, callback)
- Adds parameter callbacks
Request Properties
-
req.body: Contains parsed request body (requires body-parser)
-
req.params: Route parameters
-
req.query: Parsed query string
-
req.headers: Request headers
-
req.cookies: Cookies (requires cookie-parser)
-
req.method: HTTP method
-
req.path: Request path
-
req.url: Full URL
-
req.ip: Remote IP address
-
req.protocol: Request protocol (http or https)
Request Methods
-
req.get(header): Returns header value
-
req.is(type): Checks if content type matches
-
req.accepts(types): Checks if types are acceptable
-
req.range(size): Parses range header
Response Methods
-
res.json(obj): Sends JSON response
-
res.send(body): Sends response
-
res.status(code): Sets status code
-
res.sendStatus(code): Sets status and sends status message
-
res.set(field, value): Sets response header
-
res.cookie(name, value, options): Sets cookie
-
res.clearCookie(name): Clears cookie
-
res.redirect([status], path): Redirects to path
-
res.render(view, locals): Renders view template
-
res.sendFile(path): Sends file
-
res.download(path, filename): Downloads file
Workflow Patterns
REST API Design
Complete REST API Example:
const express = require('express'); const router = express.Router();
// GET /api/users - List all users router.get('/', asyncHandler(async (req, res) => { const { page = 1, limit = 10, sort = 'createdAt' } = req.query;
const users = await User.find() .sort(sort) .limit(parseInt(limit)) .skip((parseInt(page) - 1) * parseInt(limit)) .select('-password');
const total = await User.countDocuments();
res.json({ data: users, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / limit) } }); }));
// GET /api/users/:id - Get single user router.get('/:id', asyncHandler(async (req, res) => { const user = await User.findById(req.params.id).select('-password');
if (!user) { throw new NotFoundError('User not found'); }
res.json({ data: user }); }));
// POST /api/users - Create user router.post('/', validateUser, asyncHandler(async (req, res) => { const { email, password, name } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
throw new ValidationError('Email already exists');
}
const user = await User.create({ email, password, name });
res.status(201).json({
data: user.toJSON(),
message: 'User created successfully'
});
}) );
// PUT /api/users/:id - Update user router.put('/:id', requireAuth, validateUserUpdate, asyncHandler(async (req, res) => { const user = await User.findByIdAndUpdate( req.params.id, req.body, { new: true, runValidators: true } ).select('-password');
if (!user) {
throw new NotFoundError('User not found');
}
res.json({
data: user,
message: 'User updated successfully'
});
}) );
// DELETE /api/users/:id - Delete user router.delete('/:id', requireAuth, asyncHandler(async (req, res) => { const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
res.json({ message: 'User deleted successfully' });
}) );
module.exports = router;
Authentication
JWT Authentication:
const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs');
// Register router.post('/register', validateRegistration, asyncHandler(async (req, res) => { const { email, password, name } = req.body;
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
throw new ValidationError('Email already registered');
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const user = await User.create({
email,
password: hashedPassword,
name
});
// Generate token
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.status(201).json({
data: {
user: user.toJSON(),
token
}
});
}) );
// Login router.post('/login', validateLogin, asyncHandler(async (req, res) => { const { email, password } = req.body;
// Find user
const user = await User.findOne({ email });
if (!user) {
throw new ValidationError('Invalid credentials');
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new ValidationError('Invalid credentials');
}
// Generate token
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({
data: {
user: user.toJSON(),
token
}
});
}) );
// Refresh token router.post('/refresh', asyncHandler(async (req, res) => { const { refreshToken } = req.body;
if (!refreshToken) {
throw new ValidationError('Refresh token required');
}
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
const token = jwt.sign(
{ userId: decoded.userId, email: decoded.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ data: { token } });
}) );
// Auth middleware function requireAuth(req, res, next) { const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new AuthenticationError('No token provided'); }
const token = authHeader.split(' ')[1];
try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { throw new AuthenticationError('Invalid token'); } }
// Role-based authorization function requireRole(...roles) { return async (req, res, next) => { const user = await User.findById(req.user.userId);
if (!user || !roles.includes(user.role)) {
throw new ForbiddenError('Insufficient permissions');
}
next();
}; }
Validation
Input Validation with express-validator:
const { body, param, query, validationResult } = require('express-validator');
// Validation middleware const validate = (validations) => { return async (req, res, next) => { await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
}
next();
}; };
// User validation rules const userValidationRules = { create: validate([ body('email') .isEmail() .normalizeEmail() .withMessage('Invalid email address'), 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() .isLength({ min: 2, max: 50 }) .withMessage('Name must be between 2 and 50 characters') ]),
update: validate([ param('id') .isMongoId() .withMessage('Invalid user ID'), body('email') .optional() .isEmail() .normalizeEmail(), body('name') .optional() .trim() .isLength({ min: 2, max: 50 }) ]),
list: validate([ query('page') .optional() .isInt({ min: 1 }) .toInt(), query('limit') .optional() .isInt({ min: 1, max: 100 }) .toInt() ]) };
// Using validation router.post('/users', userValidationRules.create, createUser); router.put('/users/:id', userValidationRules.update, updateUser); router.get('/users', userValidationRules.list, listUsers);
Database Integration
MongoDB with Mongoose:
const mongoose = require('mongoose');
// Connect to database async function connectDB() { try { await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); console.log('MongoDB connected'); } catch (error) { console.error('MongoDB connection error:', error); process.exit(1); } }
// User model const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true }, password: { type: String, required: true }, name: { type: String, required: true }, role: { type: String, enum: ['user', 'admin'], default: 'user' } }, { timestamps: true });
userSchema.methods.toJSON = function() { const user = this.toObject(); delete user.password; return user; };
const User = mongoose.model('User', userSchema);
// CRUD operations router.get('/users', asyncHandler(async (req, res) => { const users = await User.find().select('-password'); res.json({ data: users }); }));
router.post('/users', asyncHandler(async (req, res) => { const user = await User.create(req.body); res.status(201).json({ data: user }); }));
router.put('/users/:id', asyncHandler(async (req, res) => { const user = await User.findByIdAndUpdate( req.params.id, req.body, { new: true, runValidators: true } ); res.json({ data: user }); }));
router.delete('/users/:id', asyncHandler(async (req, res) => { await User.findByIdAndDelete(req.params.id); res.json({ message: 'User deleted' }); }));
Testing
API Testing with Jest and Supertest:
const request = require('supertest'); const app = require('../app'); const User = require('../models/User');
describe('User API', () => { beforeEach(async () => { await User.deleteMany({}); });
describe('POST /api/users', () => { it('should create a new user', async () => { const userData = { email: 'test@example.com', password: 'Password123', name: 'Test User' };
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.data).toHaveProperty('email', userData.email);
expect(response.body.data).not.toHaveProperty('password');
});
it('should return 400 for invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'invalid-email',
password: 'Password123',
name: 'Test'
})
.expect(400);
expect(response.body).toHaveProperty('error');
});
});
describe('GET /api/users/:id', () => { it('should return user by id', async () => { const user = await User.create({ email: 'test@example.com', password: 'hashed', name: 'Test User' });
const response = await request(app)
.get(`/api/users/${user._id}`)
.expect(200);
expect(response.body.data).toHaveProperty('email', user.email);
});
it('should return 404 for non-existent user', async () => {
const response = await request(app)
.get('/api/users/507f1f77bcf86cd799439011')
.expect(404);
expect(response.body).toHaveProperty('error');
});
});
describe('Authentication', () => { it('should require authentication for protected routes', async () => { await request(app) .get('/api/protected') .expect(401); });
it('should allow access with valid token', async () => {
const token = jwt.sign({ userId: '123' }, process.env.JWT_SECRET);
await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
}); });
Best Practices
Security
Security Headers with Helmet:
const helmet = require('helmet');
// Use helmet for security headers 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://example.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://example.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');
// General API rate limiter const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP', standardHeaders: true, legacyHeaders: false });
app.use('/api/', apiLimiter);
// Strict rate limiter for authentication const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, skipSuccessfulRequests: true });
app.use('/api/login', authLimiter); app.use('/api/register', authLimiter);
// Custom key generator const customLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 100, keyGenerator: (req) => { return req.user?.id || req.ip; } });
Input Sanitization:
const mongoSanitize = require('express-mongo-sanitize'); const xss = require('xss-clean');
// Prevent NoSQL injection app.use(mongoSanitize());
// Prevent XSS attacks app.use(xss());
// Custom sanitization middleware function sanitizeInput(req, res, next) { if (req.body) { Object.keys(req.body).forEach(key => { if (typeof req.body[key] === 'string') { req.body[key] = req.body[key].trim(); } }); } next(); }
app.use(sanitizeInput);
Performance
Response Compression:
const compression = require('compression');
// Enable compression app.use(compression({ level: 6, threshold: 1024, filter: (req, res) => { if (req.headers['x-no-compression']) { return false; } return compression.filter(req, res); } }));
Caching:
// Simple in-memory cache const cache = new Map();
function cacheMiddleware(duration) { return (req, res, next) => { const key = req.originalUrl; const cached = cache.get(key);
if (cached && Date.now() < cached.expiry) {
return res.json(cached.data);
}
res.originalJson = res.json;
res.json = (data) => {
cache.set(key, {
data,
expiry: Date.now() + duration * 1000
});
res.originalJson(data);
};
next();
}; }
// Use cache app.get('/api/users', cacheMiddleware(60), getUsers);
// Redis cache const redis = require('redis'); const client = redis.createClient();
async function redisCache(duration) {
return async (req, res, next) => {
const key = cache:${req.originalUrl};
const cached = await client.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
res.originalJson = res.json;
res.json = async (data) => {
await client.setEx(key, duration, JSON.stringify(data));
res.originalJson(data);
};
next();
}; }
Request Timeout:
function timeout(ms) { return (req, res, next) => { req.setTimeout(ms, () => { res.status(408).json({ error: 'Request timeout' }); }); next(); }; }
app.use(timeout(30000)); // 30 seconds
Error Handling
Centralized Error Handling:
// Custom error classes class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } }
class ValidationError extends AppError { constructor(message) { super(message, 400); } }
class AuthenticationError extends AppError { constructor(message) { super(message, 401); } }
class NotFoundError extends AppError { constructor(message) { super(message, 404); } }
// Error handler function errorHandler(err, req, res, next) { let error = { ...err }; error.message = err.message;
// Log error console.error(err);
// Mongoose validation error if (err.name === 'ValidationError') { const message = Object.values(err.errors).map(e => e.message).join(', '); error = new ValidationError(message); }
// Mongoose duplicate key
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
error = new ValidationError(${field} already exists);
}
// JWT errors if (err.name === 'JsonWebTokenError') { error = new AuthenticationError('Invalid token'); }
if (err.name === 'TokenExpiredError') { error = new AuthenticationError('Token expired'); }
res.status(error.statusCode || 500).json({ error: { message: error.message || 'Server error', ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) } }); }
app.use(errorHandler);
Logging
Morgan and Winston:
const morgan = require('morgan'); const winston = require('winston');
// Winston logger const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), 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 HTTP logging app.use(morgan('combined', { stream: { write: (message) => logger.info(message.trim()) } }));
// Custom logging middleware app.use((req, res, next) => { logger.info({ method: req.method, url: req.url, ip: req.ip, userAgent: req.get('user-agent') }); next(); });
API Versioning
URL Versioning:
// Version 1 routes const v1Router = express.Router(); v1Router.get('/users', getUsersV1); app.use('/api/v1', v1Router);
// Version 2 routes const v2Router = express.Router(); v2Router.get('/users', getUsersV2); app.use('/api/v2', v2Router);
Header Versioning:
function apiVersion(version) { return (req, res, next) => { const requestedVersion = req.get('API-Version') || '1.0';
if (requestedVersion === version) {
next();
} else {
next('route');
}
}; }
app.get('/api/users', apiVersion('1.0'), getUsersV1); app.get('/api/users', apiVersion('2.0'), getUsersV2);
Examples
- Basic Express Server
const express = require('express'); const app = express();
app.use(express.json());
app.get('/', (req, res) => { res.json({ message: 'Hello Express!' }); });
app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }); });
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(Server running on port ${PORT});
});
- Complete REST API
const express = require('express'); const mongoose = require('mongoose'); const app = express();
// Middleware app.use(express.json()); app.use(express.urlencoded({ extended: true }));
// Models const Product = mongoose.model('Product', { name: { type: String, required: true }, price: { type: Number, required: true }, description: String, inStock: { type: Boolean, default: true } });
// Routes app.get('/api/products', async (req, res, next) => { try { const products = await Product.find(); res.json({ data: products }); } catch (error) { next(error); } });
app.get('/api/products/:id', async (req, res, next) => { try { const product = await Product.findById(req.params.id); if (!product) { return res.status(404).json({ error: 'Product not found' }); } res.json({ data: product }); } catch (error) { next(error); } });
app.post('/api/products', async (req, res, next) => { try { const product = await Product.create(req.body); res.status(201).json({ data: product }); } catch (error) { next(error); } });
app.put('/api/products/:id', async (req, res, next) => { try { const product = await Product.findByIdAndUpdate( req.params.id, req.body, { new: true, runValidators: true } ); if (!product) { return res.status(404).json({ error: 'Product not found' }); } res.json({ data: product }); } catch (error) { next(error); } });
app.delete('/api/products/:id', async (req, res, next) => { try { const product = await Product.findByIdAndDelete(req.params.id); if (!product) { return res.status(404).json({ error: 'Product not found' }); } res.json({ message: 'Product deleted' }); } catch (error) { next(error); } });
// Error handler app.use((err, req, res, next) => { console.error(err); res.status(500).json({ error: err.message }); });
// Start server mongoose.connect('mongodb://localhost/shop') .then(() => { app.listen(3000, () => console.log('Server running')); });
- Authentication System
const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const app = express();
app.use(express.json());
const users = new Map(); // In-memory storage
// Register app.post('/api/register', async (req, res) => { const { email, password, name } = req.body;
if (users.has(email)) { return res.status(400).json({ error: 'Email already exists' }); }
const hashedPassword = await bcrypt.hash(password, 10);
users.set(email, { email, password: hashedPassword, name, id: Date.now().toString() });
res.status(201).json({ message: 'User created' }); });
// Login app.post('/api/login', async (req, res) => { const { email, password } = req.body;
const user = users.get(email); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); }
const isValid = await bcrypt.compare(password, user.password); if (!isValid) { return res.status(401).json({ error: 'Invalid credentials' }); }
const token = jwt.sign( { userId: user.id, email: user.email }, 'secret-key', { expiresIn: '24h' } );
res.json({ token }); });
// Protected route app.get('/api/profile', (req, res) => { const token = req.headers.authorization?.split(' ')[1];
if (!token) { return res.status(401).json({ error: 'No token' }); }
try { const decoded = jwt.verify(token, 'secret-key'); const user = Array.from(users.values()).find(u => u.id === decoded.userId);
res.json({
email: user.email,
name: user.name
});
} catch (error) { res.status(401).json({ error: 'Invalid token' }); } });
app.listen(3000);
See EXAMPLES.md for 15+ additional examples covering file uploads, CORS, rate limiting, WebSockets, testing, deployment, and more.
Summary
This Express.js development skill covers:
-
Core Concepts: Application setup, routing, middleware, request/response handling, error handling
-
API Reference: Complete reference for Express methods and properties
-
Workflow Patterns: REST API design, authentication, validation, database integration, testing
-
Best Practices: Security (helmet, CORS, rate limiting), performance (compression, caching), error handling, logging, API versioning
-
Real-world Examples: Complete implementations for common use cases
The patterns and examples are based on Express.js best practices (Trust Score: 9) and represent modern Node.js backend development standards.