[go: up one dir, main page]

0% found this document useful (0 votes)
226 views59 pages

Complete Express - Js Documentation

The Complete Express.js Documentation provides a comprehensive guide to using the Express.js framework, covering installation, basic concepts, routing, middleware, and error handling. It highlights key features such as its minimalist design, robust routing system, and extensive middleware ecosystem. The document also includes practical examples and best practices for building web applications with Express.js.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
226 views59 pages

Complete Express - Js Documentation

The Complete Express.js Documentation provides a comprehensive guide to using the Express.js framework, covering installation, basic concepts, routing, middleware, and error handling. It highlights key features such as its minimalist design, robust routing system, and extensive middleware ecosystem. The document also includes practical examples and best practices for building web applications with Express.js.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 59

Complete Express.

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

10. Static Files


11. Database Integration
12. Security

13. Testing
14. Deployment

15. Best Practices


16. Common Patterns

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

Focus on high performance


Super-high test coverage
HTTP helpers (redirection, caching, etc.)

View system supporting 14+ template engines


Content negotiation
Executable for generating applications quickly

Why Use Express.js?


Simplicity: Easy to learn and use
Flexibility: Unopinionated framework allowing architectural freedom

Performance: Built on Node.js's non-blocking I/O model


Ecosystem: Vast middleware ecosystem

Community: Large, active community and extensive documentation

Installation and Setup

Prerequisites
Node.js (version 12 or higher recommended)
npm or yarn package manager

Installing Express

bash

# Create a new project directory


mkdir my-express-app
cd my-express-app

# Initialize npm
npm init -y

# Install Express
npm install express

# Install development dependencies (optional)


npm install --save-dev nodemon

Basic Express Application

javascript
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {


res.send('Hello, World!');
});

app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});

Using Express Generator

bash

# Install Express generator globally


npm install -g express-generator

# Generate an application
express --view=ejs myapp
cd myapp
npm install

# Start the application


npm start

Basic Concepts

The Express Application Object


The Express application object represents the Express application and has methods for routing HTTP
requests, configuring middleware, rendering HTML views, and registering template engines.

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}`);
});

// Handle all HTTP methods


app.all('/secret', (req, res) => {
res.send('Accessing the secret section...');
});

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/

Modular Application Structure

javascript
// app.js
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');

const indexRouter = require('./routes/index');


const usersRouter = require('./routes/users');
const apiRouter = require('./routes/api');

const app = express();

// View engine setup


app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 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);
});

app.use((err, req, res, next) => {


res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

Routing

Basic Routing

javascript

// Route methods
app.get('/about', (req, res) => {
res.send('About page');
});

app.post('/contact', (req, res) => {


res.send('Contact form submitted');
});

// Route patterns
app.get('/users/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});

// Multiple route parameters


app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(`User: ${req.params.userId}, Book: ${req.params.bookId}`);
});

// Optional parameters
app.get('/posts/:year/:month?', (req, res) => {
res.send(`Year: ${req.params.year}, Month: ${req.params.month || 'all'}`);
});

Route Parameters and Query Strings

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();

// Middleware specific to this router


router.use((req, res, next) => {
console.log('Users router middleware');
next();
});

// Route definitions
router.get('/', (req, res) => {
res.send('Users list');
});

router.get('/:id', (req, res) => {


res.send(`User ${req.params.id}`);
});

router.post('/', (req, res) => {


res.send('Create user');
});

module.exports = router;

// In app.js
const usersRouter = require('./routes/users');
app.use('/users', usersRouter);

Route Handlers and Multiple Handlers

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();
};

const handler2 = (req, res, next) => {


console.log('Handler 2');
next();
};

const handler3 = (req, res) => {


res.send('Final handler');
};

app.get('/array', [handler1, handler2, handler3]);

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

// Middleware for all routes


app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
});

// Middleware for specific paths


app.use('/admin', (req, res, next) => {
console.log('Admin area accessed');
next();
});

Router-Level Middleware

javascript
const router = express.Router();

router.use((req, res, next) => {


console.log('Router middleware');
next();
});

router.get('/test', (req, res) => {


res.send('Test route');
});

app.use('/api', router);

Built-in Middleware

javascript

// 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'));

// 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'));

// Helmet - Security headers


const helmet = require('helmet');
app.use(helmet());

// CORS - Cross-Origin Resource Sharing


const cors = require('cors');
app.use(cors());

// Compression
const compression = require('compression');
app.use(compression());

Custom Middleware Examples

javascript
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}

// Verify token logic here


req.user = { id: 1, name: 'John Doe' }; // Mock user
next();
};

// Rate limiting middleware


const rateLimit = (maxRequests, windowMs) => {
const requests = new Map();

return (req, res, next) => {


const clientId = req.ip;
const currentTime = Date.now();

if (!requests.has(clientId)) {
requests.set(clientId, { count: 1, startTime: currentTime });
return next();
}

const clientData = requests.get(clientId);

if (currentTime - clientData.startTime > windowMs) {


clientData.count = 1;
clientData.startTime = currentTime;
} else {
clientData.count++;
}

if (clientData.count > maxRequests) {


return res.status(429).json({ error: 'Too many requests' });
}

next();
};
};

// Usage
app.use('/api', rateLimit(100, 60000)); // 100 requests per minute
app.use('/protected', authenticate);

Request and Response Objects

Request Object (req)

javascript

app.get('/request-info', (req, res) => {


const requestInfo = {
// Basic properties
method: req.method,
url: req.url,
originalUrl: req.originalUrl,
path: req.path,

// Headers
headers: req.headers,
userAgent: req.get('User-Agent'),

// Parameters and query


params: req.params,
query: req.query,
body: req.body,

// 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);
});

Response Object (res)


javascript

app.get('/response-methods', (req, res) => {


// Set status code
res.status(200);

// Set headers
res.set('X-Custom-Header', 'Custom Value');
res.type('json');

// Send response
res.json({ message: 'Success' });
});

// Different response methods


app.get('/send-text', (req, res) => {
res.send('Plain text response');
});

app.get('/send-json', (req, res) => {


res.json({ message: 'JSON response' });
});

app.get('/send-file', (req, res) => {


res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.get('/redirect', (req, res) => {


res.redirect('/new-location');
});

app.get('/download', (req, res) => {


res.download(path.join(__dirname, 'files', 'document.pdf'));
});

app.get('/render', (req, res) => {


res.render('index', { title: 'My App', message: 'Hello World' });
});

Working with Cookies

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');
});

// Read cookies (requires cookie-parser middleware)


app.get('/read-cookie', (req, res) => {
const username = req.cookies.username;
res.send(`Username: ${username || 'Not set'}`);
});

Template Engines

Setting Up EJS

javascript

// Set EJS as template engine


app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

// Render template
app.get('/', (req, res) => {
const data = {
title: 'My Express App',
users: ['Alice', 'Bob', 'Charlie'],
isLoggedIn: true
};
res.render('index', data);
});
html

<!-- views/index.ejs -->


<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>

<% if (isLoggedIn) { %>


<p>Welcome back!</p>
<% } else { %>
<p>Please log in.</p>
<% } %>

<ul>
<% users.forEach(user => { %>
<li><%= user %></li>
<% }) %>
</ul>
</body>
</html>

Setting Up Handlebars

javascript

const exphbs = require('express-handlebars');

app.engine('handlebars', exphbs.engine());
app.set('view engine', 'handlebars');

app.get('/', (req, res) => {


res.render('home', {
title: 'Home Page',
users: [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
]
});
});
html

<!-- views/home.handlebars -->


<h1>{{title}}</h1>
<ul>
{{#each users}}
<li>{{name}} ({{age}} years old)</li>
{{/each}}
</ul>

Error Handling

Basic Error Handling

javascript

// Synchronous error handling


app.get('/sync-error', (req, res, next) => {
try {
// Some operation that might throw
throw new Error('Something went wrong');
} catch (err) {
next(err); // Pass error to error handler
}
});

// Asynchronous error handling


app.get('/async-error', async (req, res, next) => {
try {
await someAsyncOperation();
res.send('Success');
} catch (err) {
next(err);
}
});

Custom Error Classes

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);
}
}

class ValidationError extends AppError {


constructor(message) {
super(message, 400);
}
}

class NotFoundError extends AppError {


constructor(message = 'Resource not found') {
super(message, 404);
}
}

// 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);
}
});

Global Error Handler

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);
});

// Global error handling middleware


app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';

if (process.env.NODE_ENV === 'development') {


// Development error response
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
// Production error response
if (err.isOperational) {
// Operational errors: send message to client
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
// Programming errors: don't leak error details
console.error('ERROR:', err);
res.status(500).json({
status: 'error',
message: 'Something went wrong!'
});
}
}
});

Async Error Wrapper

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

Serving Static Files

javascript

// Serve static files from 'public' directory


app.use(express.static('public'));

// Serve from multiple directories


app.use(express.static('public'));
app.use(express.static('files'));

// Serve static files with virtual path prefix


app.use('/static', express.static('public'));

// Set cache control for static files


app.use('/static', express.static('public', {
maxAge: '1d', // Cache for 1 day
etag: false
}));

Advanced Static File Configuration

javascript
const path = require('path');

// Custom static file serving with options


app.use('/assets', express.static(path.join(__dirname, 'public'), {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: (res, path, stat) => {
res.set('x-timestamp', Date.now());
}
}));

Database Integration

MongoDB with Mongoose

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 }
});

const User = mongoose.model('User', userSchema);

// Routes with database operations


app.get('/api/users', async (req, res, next) => {
try {
const users = await User.find().select('-password');
res.json(users);
} catch (err) {
next(err);
}
});

app.post('/api/users', async (req, res, next) => {


try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (err) {
next(err);
}
});

app.get('/api/users/:id', async (req, res, next) => {


try {
const user = await User.findById(req.params.id).select('-password');
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.json(user);
} catch (err) {
next(err);
}
});

PostgreSQL with Sequelize

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

Basic Security Middleware

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');

// Set security HTTP headers


app.use(helmet());

// 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);

// Body parser middleware (limit size)


app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

// Data sanitization against NoSQL query injection


app.use(mongoSanitize());

// Data sanitization against XSS


app.use(xss());

// Prevent parameter pollution


app.use(hpp({
whitelist: ['sort', 'fields'] // Allow duplicate query parameters for these fields
}));

Authentication and Authorization

javascript
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// Generate JWT token


const signToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN
});
};

// Login route
app.post('/api/auth/login', async (req, res, next) => {
try {
const { email, password } = req.body;

// Check if email and password exist


if (!email || !password) {
return res.status(400).json({
status: 'fail',
message: 'Please provide email and password'
});
}

// Check if user exists and password is correct


const user = await User.findOne({ email }).select('+password');
const isPasswordCorrect = await bcrypt.compare(password, user.password);

if (!user || !isPasswordCorrect) {
return res.status(401).json({
status: 'fail',
message: 'Incorrect email or password'
});
}

// Generate token and send response


const token = signToken(user._id);
res.json({
status: 'success',
token,
data: {
user: {
id: user._id,
name: user.name,
email: user.email
}
}
});
} catch (err) {
next(err);
}
});

// 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);

// Check if user still exists


const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return res.status(401).json({
status: 'fail',
message: 'The user belonging to this token does no longer exist.'
});
}

// Grant access to protected route


req.user = currentUser;
next();
} catch (err) {
return res.status(401).json({
status: 'fail',
message: 'Invalid token'
});
}
};

// Usage
app.get('/api/profile', protect, (req, res) => {
res.json({
status: 'success',
data: {
user: req.user
}
});
});

Testing

Unit Testing with Jest

javascript
// tests/app.test.js
const request = require('supertest');
const app = require('../app');

describe('GET /', () => {


it('should return 200 OK', async () => {
const res = await request(app)
.get('/')
.expect(200);

expect(res.text).toContain('Hello World');
});
});

describe('POST /api/users', () => {


it('should create a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
};

const res = await request(app)


.post('/api/users')
.send(userData)
.expect(201);

expect(res.body.name).toBe(userData.name);
expect(res.body.email).toBe(userData.email);
expect(res.body.password).toBeUndefined();
});

it('should return 400 for invalid data', async () => {


const invalidData = {
name: '',
email: 'invalid-email'
};

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({});
});

describe('POST /api/auth/register', () => {


it('should register a new user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
passwordConfirm: 'password123'
};

const res = await request(app)


.post('/api/auth/register')
.send(userData)
.expect(201);

expect(res.body.status).toBe('success');
expect(res.body.token).toBeDefined();
expect(res.body.data.user.email).toBe(userData.email);
});
});

describe('POST /api/auth/login', () => {


it('should login with valid credentials', async () => {
// Create user first
const user = new User({
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
});
await user.save();

const res = await request(app)


.post('/api/auth/login')
.send({
email: 'john@example.com',
password: 'password123'
})
.expect(200);

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

# Create app directory


WORKDIR /usr/src/app

# Copy package files


COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy app source


COPY . .

# Create non-root user


RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Change ownership of the working directory


RUN chown -R nextjs:nodejs /usr/src/app
USER nextjs

# Expose port
EXPOSE 3000

# Define health check


HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js

# Start the application


CMD ["node", "app.js"]

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

AWS Deployment with PM2

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');

exports.getAllUsers = catchAsync(async (req, res, next) => {


const users = await User.find();

res.status(200).json({
status: 'success',
results: users.length,
data: {
users
}
});
});

exports.getUser = catchAsync(async (req, res, next) => {


const user = await User.findById(req.params.id);

if (!user) {
return next(new AppError('No user found with that ID', 404));
}

res.status(200).json({
status: 'success',
data: {
user
}
});
});

exports.createUser = catchAsync(async (req, res, next) => {


const newUser = await User.create(req.body);

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

const winston = require('winston');

// Configure 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()
),
defaultMeta: { service: 'express-app' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});

// Add console logging in development


if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}

// Request logging middleware


const requestLogger = (req, res, next) => {
logger.info(`${req.method} ${req.url}`, {
ip: req.ip,
userAgent: req.get('User-Agent'),
timestamp: new Date().toISOString()
});
next();
};

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();
};
};

// Database query optimization


app.get('/api/users', cache(300), async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;

const users = await User.find()


.select('name email createdAt')
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.lean(); // Returns plain JavaScript objects instead of Mongoose documents

const total = await User.countDocuments();

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();

v1Router.get('/users', (req, res) => {


res.json({ version: 'v1', users: [ ] });
});

// Version 2 routes
const v2Router = express.Router();

v2Router.get('/users', (req, res) => {


res.json({
version: 'v2',
users: [ ],
meta: { total: 0, page: 1 }
});
});

// Mount versioned routes


app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// Default to latest version


app.use('/api', v2Router);

// Header-based versioning
const versionMiddleware = (req, res, next) => {
const version = req.headers['api-version'] || 'v2';
req.apiVersion = version;
next();
};

app.use('/api', versionMiddleware, (req, res, next) => {


if (req.apiVersion === 'v1') {
v1Router(req, res, next);
} else {
v2Router(req, res, next);
}
});

Common Patterns
Repository Pattern

javascript
// repositories/UserRepository.js
class UserRepository {
constructor(model) {
this.model = model;
}

async findAll(options = {}) {


const { page = 1, limit = 10, sort = '-createdAt' } = options;
const skip = (page - 1) * limit;

return await this.model


.find()
.sort(sort)
.skip(skip)
.limit(limit)
.lean();
}

async findById(id) {
return await this.model.findById(id).lean();
}

async create(data) {
const document = new this.model(data);
return await document.save();
}

async update(id, data) {


return await this.model
.findByIdAndUpdate(id, data, { new: true })
.lean();
}

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');

const userRepo = new UserRepository(User);

exports.getAllUsers = catchAsync(async (req, res, next) => {


const users = await userRepo.findAll(req.query);

res.status(200).json({
status: 'success',
data: { users }
});
});

Service Layer Pattern

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
});

// Remove password from response


const { password, ...userWithoutPassword } = user.toObject();
return userWithoutPassword;
}

async getUserById(id) {
const user = await this.userRepo.findById(id);
if (!user) {
throw new AppError('User not found', 404);
}
return user;
}

async updateUser(id, updateData) {


// Don't allow password updates through this method
delete updateData.password;
const user = await this.userRepo.update(id, updateData);
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');

const userRepo = new UserRepository(User);


const userService = new UserService(userRepo);

exports.createUser = catchAsync(async (req, res, next) => {


const user = await userService.createUser(req.body);

res.status(201).json({
status: 'success',
data: { user }
});
});

Factory Pattern for Database Handlers

javascript
// utils/handlerFactory.js
const catchAsync = require('./catchAsync');
const AppError = require('./appError');

exports.deleteOne = (Model) => catchAsync(async (req, res, next) => {


const doc = await Model.findByIdAndDelete(req.params.id);

if (!doc) {
return next(new AppError('No document found with that ID', 404));
}

res.status(204).json({
status: 'success',
data: null
});
});

exports.updateOne = (Model) => catchAsync(async (req, res, next) => {


const doc = await Model.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true
});

if (!doc) {
return next(new AppError('No document found with that ID', 404));
}

res.status(200).json({
status: 'success',
data: {
data: doc
}
});
});

exports.createOne = (Model) => catchAsync(async (req, res, next) => {


const doc = await Model.create(req.body);

res.status(201).json({
status: 'success',
data: {
data: doc
}
});
});

exports.getOne = (Model, popOptions) => catchAsync(async (req, res, next) => {


let query = Model.findById(req.params.id);
if (popOptions) query = query.populate(popOptions);
const doc = await query;

if (!doc) {
return next(new AppError('No document found with that ID', 404));
}

res.status(200).json({
status: 'success',
data: {
data: doc
}
});
});

exports.getAll = (Model) => catchAsync(async (req, res, next) => {


// Execute query
const features = new APIFeatures(Model.find(), req.query)
.filter()
.sort()
.limitFields()
.paginate();
const doc = await features.query;

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);

Advanced Query Features

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');

const server = http.createServer(app);


const io = socketIo(server, {
cors: {
origin: process.env.CLIENT_URL || "http://localhost:3000",
methods: ["GET", "POST"]
}
});

// Socket.io middleware for authentication


io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.id);
socket.user = user;
next();
} catch (err) {
next(new Error('Authentication error'));
}
});

io.on('connection', (socket) => {


console.log(`User ${socket.user.name} connected`);

// Join user to their own room


socket.join(socket.user.id);

// Handle chat messages


socket.on('send_message', async (data) => {
try {
const message = await Message.create({
content: data.content,
sender: socket.user.id,
recipient: data.recipient
});

// 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`);
});
});

// REST endpoint to get chat history


app.get('/api/messages/:userId', protect, async (req, res, next) => {
try {
const messages = await Message.find({
$or: [
{ sender: req.user.id, recipient: req.params.userId },
{ sender: req.params.userId, recipient: req.user.id }
]
}).sort({ createdAt: 1 }).populate('sender', 'name');

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.

You might also like