Express.js
Minimal and flexible Node.js web application framework. Lightweight and fast with rich middleware ecosystem. Event-driven architecture.
Application Server
Express.js
Overview
Express.js is a fast, unopinionated, and minimalist web framework for Node.js that provides a robust set of features for building web applications and APIs. As the most popular Node.js framework, Express emphasizes simplicity and flexibility through its lightweight core and extensive middleware ecosystem. It offers essential web application features while maintaining the freedom to structure applications according to specific requirements. Express serves as the foundation for countless Node.js applications, from simple REST APIs to complex enterprise web solutions, making it an essential tool in modern JavaScript development.
Details
Express.js 2025 edition continues its dominance as the standard web framework for Node.js development, with over 1.8 million weekly downloads and widespread adoption across the JavaScript ecosystem. Built on Node.js's event-driven architecture, Express leverages non-blocking I/O operations to deliver exceptional performance for concurrent requests. The framework's philosophy centers on providing essential web server functionality without imposing rigid structure, allowing developers to choose their preferred patterns and libraries. Express's middleware-based architecture enables modular application design, where each middleware function can modify request and response objects or terminate the request-response cycle. This approach facilitates code reuse, maintainability, and testing while supporting both traditional server-rendered applications and modern API-first architectures.
Key Features
- Minimalist Core: Lightweight framework with essential web server functionality
- Middleware Architecture: Modular request processing through middleware functions
- Routing System: Flexible URL routing with parameter extraction and pattern matching
- Template Engine Support: Integration with popular view engines like EJS, Pug, and Handlebars
- Static File Serving: Built-in static file server with customizable options
- Error Handling: Comprehensive error handling with custom error middleware support
Advantages and Disadvantages
Advantages
- Minimal learning curve with simple, intuitive API design
- Extensive ecosystem with thousands of middleware packages available via npm
- High performance through Node.js event-driven, non-blocking I/O model
- Flexible architecture allowing custom application structure and patterns
- Strong community support with comprehensive documentation and tutorials
- Seamless integration with modern development tools and testing frameworks
- Active development with regular updates and security patches
Disadvantages
- Callback-heavy code can lead to callback hell without proper async/await usage
- Lack of built-in structure may result in inconsistent codebases across teams
- Limited built-in features require additional packages for common functionality
- Single-threaded nature may not be optimal for CPU-intensive operations
- Requires careful error handling to prevent application crashes
- Can become complex when implementing enterprise-level features without frameworks
Reference Links
Configuration Examples
Basic Setup and Installation
# Initialize a new Node.js project
npm init -y
# Install Express.js
npm install express
# Install additional commonly used packages
npm install --save-dev nodemon
npm install dotenv
npm install helmet cors
# Basic package.json scripts
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "npm test"
}
}
# Create basic directory structure
mkdir routes middleware controllers models views public
mkdir public/css public/js public/images
Simple Express Application
// app.js - Basic Express application
const express = require('express');
const path = require('path');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// Basic middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
// Basic route
app.get('/', (req, res) => {
res.send('Hello World!');
});
// API route
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'John Doe', email: '[email protected]' },
{ id: 2, name: 'Jane Smith', email: '[email protected]' }
]);
});
// POST route with data handling
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({
error: 'Name and email are required'
});
}
const newUser = {
id: Date.now(),
name,
email,
createdAt: new Date()
};
res.status(201).json(newUser);
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({
error: 'Something went wrong!',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
module.exports = app;
Advanced Routing and Middleware
// routes/users.js - Router module
const express = require('express');
const router = express.Router();
// Middleware specific to this router
router.use((req, res, next) => {
console.log('Users route accessed at:', new Date().toISOString());
next();
});
// Route parameters and validation
router.get('/:id', (req, res, next) => {
const userId = parseInt(req.params.id);
if (isNaN(userId)) {
return res.status(400).json({ error: 'Invalid user ID' });
}
// Mock user lookup
const user = { id: userId, name: `User ${userId}` };
res.json(user);
});
// Query parameters
router.get('/', (req, res) => {
const { page = 1, limit = 10, search } = req.query;
let users = [
{ id: 1, name: 'John Doe', email: '[email protected]' },
{ id: 2, name: 'Jane Smith', email: '[email protected]' },
{ id: 3, name: 'Bob Johnson', email: '[email protected]' }
];
// Search functionality
if (search) {
users = users.filter(user =>
user.name.toLowerCase().includes(search.toLowerCase())
);
}
// Pagination
const startIndex = (page - 1) * limit;
const endIndex = startIndex + parseInt(limit);
const paginatedUsers = users.slice(startIndex, endIndex);
res.json({
users: paginatedUsers,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: users.length,
pages: Math.ceil(users.length / limit)
}
});
});
// Multiple middleware functions
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
// Mock token validation
if (token !== 'Bearer valid-token') {
return res.status(401).json({ error: 'Invalid token' });
}
req.user = { id: 1, username: 'admin' };
next();
};
const authorize = (roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
// Mock role check
if (!roles.includes(req.user.role || 'user')) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Protected routes
router.post('/', authenticate, (req, res) => {
const { name, email } = req.body;
const newUser = {
id: Date.now(),
name,
email,
createdBy: req.user.id
};
res.status(201).json(newUser);
});
router.delete('/:id', authenticate, authorize(['admin']), (req, res) => {
const userId = parseInt(req.params.id);
res.json({ message: `User ${userId} deleted by ${req.user.username}` });
});
module.exports = router;
// app.js - Using the router
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);
Middleware Implementation and Error Handling
// middleware/logger.js - Custom logging middleware
const logger = (req, res, next) => {
const start = Date.now();
// Log request
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
// Override res.end to log response time
const originalEnd = res.end;
res.end = function(...args) {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`);
originalEnd.apply(this, args);
};
next();
};
// middleware/validation.js - Request validation
const validateUser = (req, res, next) => {
const { name, email } = req.body;
const errors = [];
if (!name || name.trim().length < 2) {
errors.push('Name must be at least 2 characters long');
}
if (!email || !/\S+@\S+\.\S+/.test(email)) {
errors.push('Valid email is required');
}
if (errors.length > 0) {
return res.status(400).json({
error: 'Validation failed',
details: errors
});
}
next();
};
// middleware/rateLimit.js - Simple rate limiting
const rateLimit = (options = {}) => {
const { windowMs = 15 * 60 * 1000, max = 100 } = options;
const clients = new Map();
return (req, res, next) => {
const clientId = req.ip || req.connection.remoteAddress;
const now = Date.now();
if (!clients.has(clientId)) {
clients.set(clientId, { count: 1, resetTime: now + windowMs });
return next();
}
const client = clients.get(clientId);
if (now > client.resetTime) {
client.count = 1;
client.resetTime = now + windowMs;
return next();
}
if (client.count >= max) {
return res.status(429).json({
error: 'Too many requests',
retryAfter: Math.ceil((client.resetTime - now) / 1000)
});
}
client.count++;
next();
};
};
// Error handling middleware
const errorHandler = (err, req, res, next) => {
console.error('Error occurred:', err);
// Mongoose validation error
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(e => e.message);
return res.status(400).json({
error: 'Validation Error',
details: errors
});
}
// MongoDB duplicate key error
if (err.code === 11000) {
return res.status(409).json({
error: 'Duplicate resource',
field: Object.keys(err.keyPattern)[0]
});
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({ error: 'Invalid token' });
}
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
// Default error
res.status(err.status || 500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
};
// Async error wrapper
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
// Usage in app.js
app.use(logger);
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// Apply validation to specific routes
app.post('/api/users', validateUser, (req, res) => {
// Route handler
});
// Use async handler for async routes
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
}));
app.use(errorHandler);
Template Engine Integration and File Handling
// Template engine setup (EJS example)
const ejs = require('ejs');
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Render HTML pages
app.get('/dashboard', (req, res) => {
const data = {
title: 'Dashboard',
user: { name: 'John Doe', role: 'admin' },
stats: {
users: 150,
orders: 1200,
revenue: 45000
}
};
res.render('dashboard', data);
});
// File upload handling with multer
const multer = require('multer');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage,
limits: {
fileSize: 5 * 1024 * 1024 // 5MB limit
},
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 (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Invalid file type'));
}
}
});
// Single file upload
app.post('/api/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
filename: req.file.filename,
originalname: req.file.originalname,
size: req.file.size,
path: req.file.path
}
});
});
// Multiple file upload
app.post('/api/upload-multiple', upload.array('files', 5), (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' });
}
const fileInfo = req.files.map(file => ({
filename: file.filename,
originalname: file.originalname,
size: file.size
}));
res.json({
message: 'Files uploaded successfully',
files: fileInfo
});
});
// File download
app.get('/api/download/:filename', (req, res) => {
const filename = req.params.filename;
const filepath = path.join(__dirname, 'uploads', filename);
// Check if file exists
if (!require('fs').existsSync(filepath)) {
return res.status(404).json({ error: 'File not found' });
}
res.download(filepath, (err) => {
if (err) {
console.error('Download error:', err);
res.status(500).json({ error: 'Error downloading file' });
}
});
});
Security and Production Configuration
// security.js - Security middleware setup
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
// Security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// CORS configuration
const corsOptions = {
origin: process.env.NODE_ENV === 'production'
? ['https://yourdomain.com', 'https://www.yourdomain.com']
: ['http://localhost:3000', 'http://localhost:3001'],
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
// Compression
app.use(compression());
// Environment-specific configuration
if (process.env.NODE_ENV === 'production') {
// Trust proxy for proper IP detection
app.set('trust proxy', 1);
// Additional production security
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
}
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV
});
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
});
});
// Start server with proper error handling
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT} in ${process.env.NODE_ENV} mode`);
}).on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`Port ${PORT} is already in use`);
process.exit(1);
} else {
console.error('Server error:', err);
}
});
Testing and API Documentation
// tests/app.test.js - Testing with Jest and Supertest
const request = require('supertest');
const app = require('../app');
describe('Express App', () => {
test('GET / should return Hello World', async () => {
const response = await request(app)
.get('/')
.expect(200);
expect(response.text).toBe('Hello World!');
});
test('GET /api/users should return users array', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
expect(response.body.length).toBeGreaterThan(0);
});
test('POST /api/users should create user', async () => {
const userData = {
name: 'Test User',
email: '[email protected]'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.name).toBe(userData.name);
expect(response.body.email).toBe(userData.email);
expect(response.body.id).toBeDefined();
});
test('POST /api/users should validate input', async () => {
const response = await request(app)
.post('/api/users')
.send({})
.expect(400);
expect(response.body.error).toBe('Name and email are required');
});
});
// API documentation with Swagger/OpenAPI
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Express API',
version: '1.0.0',
description: 'A simple Express API'
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server'
}
]
},
apis: ['./routes/*.js'] // paths to files containing OpenAPI definitions
};
const specs = swaggerJSDoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
/**
* @swagger
* /api/users:
* get:
* summary: Get all users
* tags: [Users]
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* description: Page number
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* name:
* type: string
* email:
* type: string
*/