Complete Express - Js Documentation
Complete Express - Js Documentation
js Documentation
Table of Contents
1. Introduction
2. Installation and Setup
3. Basic Concepts
4. Application Structure
5. Routing
6. Middleware
7. Request and Response Objects
8. Template Engines
9. Error Handling
13. Testing
14. Deployment
Introduction
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of
features for web and mobile applications. It's the de facto standard server framework for Node.js and is
the underlying library for many other popular Node.js frameworks.
Key Features
Fast, unopinionated, minimalist web framework
Robust routing system
Prerequisites
Node.js (version 12 or higher recommended)
npm or yarn package manager
Installing Express
bash
# Initialize npm
npm init -y
# Install Express
npm install express
javascript
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
bash
# Generate an application
express --view=ejs myapp
cd myapp
npm install
Basic Concepts
javascript
const express = require('express');
const app = express();
// App configuration
app.set('view engine', 'ejs');
app.set('views', './views');
// Application-level settings
app.locals.title = 'My Express App';
HTTP Methods
Express provides methods corresponding to HTTP verbs:
javascript
// GET request
app.get('/users', (req, res) => {
res.send('GET /users');
});
// POST request
app.post('/users', (req, res) => {
res.send('POST /users');
});
// PUT request
app.put('/users/:id', (req, res) => {
res.send(`PUT /users/${req.params.id}`);
});
// DELETE request
app.delete('/users/:id', (req, res) => {
res.send(`DELETE /users/${req.params.id}`);
});
Application Structure
Recommended Directory Structure
my-express-app/
├── app.js # Application entry point
├── package.json
├── bin/
│ └── www # Server startup script
├── config/
│ ├── database.js # Database configuration
│ └── config.js # Application configuration
├── controllers/
│ ├── userController.js
│ └── authController.js
├── middleware/
│ ├── auth.js
│ └── validation.js
├── models/
│ ├── User.js
│ └── Product.js
├── routes/
│ ├── index.js
│ ├── users.js
│ └── api.js
├── views/
│ ├── layout.ejs
│ ├── index.ejs
│ └── error.ejs
├── public/
│ ├── stylesheets/
│ ├── javascripts/
│ └── images/
└── tests/
├── unit/
└── integration/
javascript
// app.js
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
// Middleware
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/api', apiRouter);
// Error handling
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
Routing
Basic Routing
javascript
// Route methods
app.get('/about', (req, res) => {
res.send('About page');
});
// Route patterns
app.get('/users/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
// Optional parameters
app.get('/posts/:year/:month?', (req, res) => {
res.send(`Year: ${req.params.year}, Month: ${req.params.month || 'all'}`);
});
javascript
// Route parameters
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID: ${userId}`);
});
// Query parameters
app.get('/search', (req, res) => {
const { q, limit, offset } = req.query;
res.json({
query: q,
limit: limit || 10,
offset: offset || 0
});
});
// Wildcard routes
app.get('/files/*', (req, res) => {
const filePath = req.params[0];
res.send(`File path: ${filePath}`);
});
Express Router
javascript
// routes/users.js
const express = require('express');
const router = express.Router();
// Route definitions
router.get('/', (req, res) => {
res.send('Users list');
});
module.exports = router;
// In app.js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);
javascript
// Single handler
app.get('/single', (req, res) => {
res.send('Single handler');
});
// Multiple handlers
app.get('/multiple',
(req, res, next) => {
console.log('First handler');
next();
},
(req, res) => {
res.send('Second handler');
}
);
// Array of handlers
const handler1 = (req, res, next) => {
console.log('Handler 1');
next();
};
Middleware
Understanding Middleware
Middleware functions are functions that have access to the request object (req), the response object
(res), and the next middleware function in the application's request-response cycle.
javascript
// Basic middleware function
const myMiddleware = (req, res, next) => {
console.log('Middleware executed');
// Modify request or response
req.customProperty = 'Hello from middleware';
next(); // Call next to continue to the next middleware
};
app.use(myMiddleware);
Types of Middleware
Application-Level Middleware
javascript
Router-Level Middleware
javascript
const router = express.Router();
app.use('/api', router);
Built-in Middleware
javascript
// Parse cookies
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Third-Party Middleware
javascript
// Morgan - HTTP request logger
const morgan = require('morgan');
app.use(morgan('combined'));
// Compression
const compression = require('compression');
app.use(compression());
javascript
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
if (!requests.has(clientId)) {
requests.set(clientId, { count: 1, startTime: currentTime });
return next();
}
next();
};
};
// Usage
app.use('/api', rateLimit(100, 60000)); // 100 requests per minute
app.use('/protected', authenticate);
javascript
// Headers
headers: req.headers,
userAgent: req.get('User-Agent'),
// Network info
ip: req.ip,
ips: req.ips,
protocol: req.protocol,
secure: req.secure,
hostname: req.hostname,
// Cookies
cookies: req.cookies,
signedCookies: req.signedCookies
};
res.json(requestInfo);
});
// Set headers
res.set('X-Custom-Header', 'Custom Value');
res.type('json');
// Send response
res.json({ message: 'Success' });
});
javascript
// Set cookies
app.get('/set-cookie', (req, res) => {
res.cookie('username', 'john_doe', {
maxAge: 900000, // 15 minutes
httpOnly: true,
secure: process.env.NODE_ENV === 'production'
});
res.send('Cookie set');
});
// Clear cookies
app.get('/clear-cookie', (req, res) => {
res.clearCookie('username');
res.send('Cookie cleared');
});
Template Engines
Setting Up EJS
javascript
// Render template
app.get('/', (req, res) => {
const data = {
title: 'My Express App',
users: ['Alice', 'Bob', 'Charlie'],
isLoggedIn: true
};
res.render('index', data);
});
html
<ul>
<% users.forEach(user => { %>
<li><%= user %></li>
<% }) %>
</ul>
</body>
</html>
Setting Up Handlebars
javascript
app.engine('handlebars', exphbs.engine());
app.set('view engine', 'handlebars');
Error Handling
javascript
javascript
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);
}
}
// Usage
app.get('/user/:id', async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
res.json(user);
} catch (err) {
next(err);
}
});
javascript
// 404 handler (should be after all routes)
app.all('*', (req, res, next) => {
const err = new AppError(`Can't find ${req.originalUrl} on this server!`, 404);
next(err);
});
javascript
// Utility function to catch async errors
const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};
// Usage
app.get('/users', catchAsync(async (req, res, next) => {
const users = await User.find();
res.json(users);
}));
Static Files
javascript
javascript
const path = require('path');
Database Integration
javascript
const mongoose = require('mongoose');
// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// Define schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
javascript
const { Sequelize, DataTypes } = require('sequelize');
// Initialize Sequelize
const sequelize = new Sequelize('postgres://user:password@localhost:5432/mydb');
// Define model
const User = sequelize.define('User', {
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
});
// Sync database
sequelize.sync();
// Routes
app.get('/api/users', async (req, res, next) => {
try {
const users = await User.findAll();
res.json(users);
} catch (err) {
next(err);
}
});
Security
javascript
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
// Rate limiting
const limiter = rateLimit({
max: 100, // limit each IP to 100 requests per windowMs
windowMs: 15 * 60 * 1000, // 15 minutes
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api', limiter);
javascript
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
// Login route
app.post('/api/auth/login', async (req, res, next) => {
try {
const { email, password } = req.body;
if (!user || !isPasswordCorrect) {
return res.status(401).json({
status: 'fail',
message: 'Incorrect email or password'
});
}
// Protect middleware
const protect = async (req, res, next) => {
try {
// Get token
let token;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return res.status(401).json({
status: 'fail',
message: 'You are not logged in! Please log in to get access.'
});
}
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Usage
app.get('/api/profile', protect, (req, res) => {
res.json({
status: 'success',
data: {
user: req.user
}
});
});
Testing
javascript
// tests/app.test.js
const request = require('supertest');
const app = require('../app');
expect(res.text).toContain('Hello World');
});
});
expect(res.body.name).toBe(userData.name);
expect(res.body.email).toBe(userData.email);
expect(res.body.password).toBeUndefined();
});
await request(app)
.post('/api/users')
.send(invalidData)
.expect(400);
});
});
Integration Testing
javascript
// tests/integration/auth.test.js
const request = require('supertest');
const app = require('../../app');
const User = require('../../models/User');
describe('Authentication', () => {
beforeEach(async () => {
await User.deleteMany({});
});
expect(res.body.status).toBe('success');
expect(res.body.token).toBeDefined();
expect(res.body.data.user.email).toBe(userData.email);
});
});
expect(res.body.status).toBe('success');
expect(res.body.token).toBeDefined();
});
});
});
Deployment
Environment Configuration
javascript
// config/config.js
module.exports = {
development: {
port: process.env.PORT || 3000,
database: process.env.DATABASE_URL || 'mongodb://localhost:27017/myapp-dev',
jwtSecret: process.env.JWT_SECRET || 'dev-secret-key',
logLevel: 'debug'
},
production: {
port: process.env.PORT || 8080,
database: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
logLevel: 'error'
},
test: {
port: process.env.PORT || 3001,
database: process.env.TEST_DATABASE_URL || 'mongodb://localhost:27017/myapp-test',
jwtSecret: 'test-secret-key',
logLevel: 'silent'
}
};
Docker Configuration
dockerfile
# Dockerfile
FROM node:16-alpine
# Install dependencies
RUN npm ci --only=production
# Expose port
EXPOSE 3000
yaml
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=mongodb://mongo:27017/myapp
- JWT_SECRET=your-secret-key
depends_on:
- mongo
restart: unless-stopped
mongo:
image: mongo:5.0
volumes:
- mongo-data:/data/db
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
volumes:
mongo-data:
Heroku Deployment
json
// package.json scripts for Heroku
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "jest",
"heroku-postbuild": "npm run build"
},
"engines": {
"node": "16.x",
"npm": "8.x"
}
}
javascript
// Procfile
web: node app.js
javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-express-app',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log',
time: true
}]
};
Best Practices
Code Organization
javascript
// controllers/userController.js
const User = require('../models/User');
const catchAsync = require('../utils/catchAsync');
const AppError = require('../utils/appError');
res.status(200).json({
status: 'success',
results: users.length,
data: {
users
}
});
});
if (!user) {
return next(new AppError('No user found with that ID', 404));
}
res.status(200).json({
status: 'success',
data: {
user
}
});
});
res.status(201).json({
status: 'success',
data: {
user: newUser
}
});
});
Input Validation
javascript
const { body, validationResult } = require('express-validator');
// Validation rules
const userValidationRules = () => {
return [
body('name')
.notEmpty()
.withMessage('Name is required')
.isLength({ min: 2, max: 50 })
.withMessage('Name must be between 2 and 50 characters'),
body('email')
.isEmail()
.withMessage('Please provide a valid email')
.normalizeEmail(),
body('password')
.isLength({ min: 8 })
.withMessage('Password must be at least 8 characters long')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('Password must contain at least one lowercase letter, one uppercase letter, and one number'),
body('age')
.optional()
.isInt({ min: 0, max: 120 })
.withMessage('Age must be a valid number between 0 and 120')
];
};
// Validation middleware
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
status: 'fail',
message: 'Validation failed',
errors: errors.array()
});
}
next();
};
// Usage
app.post('/api/users', userValidationRules(), validate, userController.createUser);
Logging
javascript
app.use(requestLogger);
module.exports = logger;
Performance Optimization
javascript
const compression = require('compression');
const helmet = require('helmet');
// Compression middleware
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
level: 6,
threshold: 1024
}));
// Caching middleware
const cache = (duration) => {
return (req, res, next) => {
const key = req.originalUrl;
const cachedResponse = myCache.get(key);
if (cachedResponse) {
return res.json(cachedResponse);
}
res.sendResponse = res.json;
res.json = (body) => {
myCache.set(key, body, duration);
res.sendResponse(body);
};
next();
};
};
res.json({
status: 'success',
results: users.length,
pagination: {
page,
pages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1
},
data: { users }
});
} catch (err) {
next(err);
}
});
API Versioning
javascript
// Version 1 routes
const v1Router = express.Router();
// Version 2 routes
const v2Router = express.Router();
// Header-based versioning
const versionMiddleware = (req, res, next) => {
const version = req.headers['api-version'] || 'v2';
req.apiVersion = version;
next();
};
Common Patterns
Repository Pattern
javascript
// repositories/UserRepository.js
class UserRepository {
constructor(model) {
this.model = model;
}
async findById(id) {
return await this.model.findById(id).lean();
}
async create(data) {
const document = new this.model(data);
return await document.save();
}
async delete(id) {
return await this.model.findByIdAndDelete(id);
}
async findByEmail(email) {
return await this.model.findOne({ email }).lean();
}
}
module.exports = UserRepository;
// Usage in controller
const UserRepository = require('../repositories/UserRepository');
const User = require('../models/User');
res.status(200).json({
status: 'success',
data: { users }
});
});
javascript
// services/UserService.js
const UserRepository = require('../repositories/UserRepository');
const AppError = require('../utils/appError');
const bcrypt = require('bcryptjs');
class UserService {
constructor(userRepository) {
this.userRepo = userRepository;
}
async createUser(userData) {
// Check if user already exists
const existingUser = await this.userRepo.findByEmail(userData.email);
if (existingUser) {
throw new AppError('User with this email already exists', 400);
}
// Hash password
const hashedPassword = await bcrypt.hash(userData.password, 12);
// Create user
const user = await this.userRepo.create({
...userData,
password: hashedPassword
});
async getUserById(id) {
const user = await this.userRepo.findById(id);
if (!user) {
throw new AppError('User not found', 404);
}
return user;
}
async deleteUser(id) {
const user = await this.userRepo.delete(id);
if (!user) {
throw new AppError('User not found', 404);
}
return user;
}
}
module.exports = UserService;
// Usage in controller
const UserService = require('../services/UserService');
const UserRepository = require('../repositories/UserRepository');
const User = require('../models/User');
res.status(201).json({
status: 'success',
data: { user }
});
});
javascript
// utils/handlerFactory.js
const catchAsync = require('./catchAsync');
const AppError = require('./appError');
if (!doc) {
return next(new AppError('No document found with that ID', 404));
}
res.status(204).json({
status: 'success',
data: null
});
});
if (!doc) {
return next(new AppError('No document found with that ID', 404));
}
res.status(200).json({
status: 'success',
data: {
data: doc
}
});
});
res.status(201).json({
status: 'success',
data: {
data: doc
}
});
});
if (!doc) {
return next(new AppError('No document found with that ID', 404));
}
res.status(200).json({
status: 'success',
data: {
data: doc
}
});
});
res.status(200).json({
status: 'success',
results: doc.length,
data: {
data: doc
}
});
});
// Usage
const factory = require('../utils/handlerFactory');
const User = require('../models/User');
exports.getAllUsers = factory.getAll(User);
exports.getUser = factory.getOne(User);
exports.createUser = factory.createOne(User);
exports.updateUser = factory.updateOne(User);
exports.deleteUser = factory.deleteOne(User);
javascript
// utils/apiFeatures.js
class APIFeatures {
constructor(query, queryString) {
this.query = query;
this.queryString = queryString;
}
filter() {
const queryObj = { ...this.queryString };
const excludedFields = ['page', 'sort', 'limit', 'fields'];
excludedFields.forEach(el => delete queryObj[el]);
// Advanced filtering
let queryStr = JSON.stringify(queryObj);
queryStr = queryStr.replace(/\b(gte|gt|lte|lt)\b/g, match => `${match}`);
this.query = this.query.find(JSON.parse(queryStr));
return this;
}
sort() {
if (this.queryString.sort) {
const sortBy = this.queryString.sort.split(',').join(' ');
this.query = this.query.sort(sortBy);
} else {
this.query = this.query.sort('-createdAt');
}
return this;
}
limitFields() {
if (this.queryString.fields) {
const fields = this.queryString.fields.split(',').join(' ');
this.query = this.query.select(fields);
} else {
this.query = this.query.select('-__v');
}
return this;
}
paginate() {
const page = this.queryString.page * 1 || 1;
const limit = this.queryString.limit * 1 || 100;
const skip = (page - 1) * limit;
this.query = this.query.skip(skip).limit(limit);
return this;
}
}
module.exports = APIFeatures;
WebSocket Integration
javascript
const http = require('http');
const socketIo = require('socket.io');
// Send to recipient
socket.to(data.recipient).emit('new_message', {
id: message._id,
content: message.content,
sender: {
id: socket.user.id,
name: socket.user.name
},
timestamp: message.createdAt
});
} catch (err) {
socket.emit('error', { message: 'Failed to send message' });
}
});
socket.on('disconnect', () => {
console.log(`User ${socket.user.name} disconnected`);
});
});
res.json({
status: 'success',
data: { messages }
});
} catch (err) {
next(err);
}
});
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
This comprehensive Express.js documentation covers all the essential aspects you need to build robust
web applications. From basic setup to advanced patterns, security considerations, testing strategies,
and deployment options, this guide provides practical examples and best practices that you can apply in
real-world projects.
The documentation is structured to help both beginners getting started with Express.js and
experienced developers looking for advanced patterns and optimization techniques. Each section
includes working code examples that demonstrate the concepts in action.