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 1. 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.