Express.
js Backend Development Guide
Table of Contents
1. Express.js Setup and Basics
2. Routing and Route Parameters
3. Middleware
4. Request/Response Handling
5. Error Handling
6. Database Integration
7. Authentication & Authorization
8. File Upload and Static Files
9. Environment Configuration
10. Testing and Debugging
Express.js Setup and Basics
Installation
bash
npm init -y
npm install express
npm install -D nodemon
Basic Server
javascript
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
// Basic route
app.get('/', (req, res) => {
res.json({ message: 'Hello Express!' });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Project Structure
project/
├── app.js # Main application file
├── routes/ # Route handlers
│ ├── users.js
│ └── auth.js
├── middleware/ # Custom middleware
├── models/ # Database models
├── controllers/ # Business logic
├── config/ # Configuration files
└── public/ # Static files
Routing and Route Parameters
Basic Routes
javascript
const express = require('express');
const app = express();
// HTTP Methods
app.get('/users', (req, res) => {
res.json({ users: [] });
});
app.post('/users', (req, res) => {
const { name, email } = req.body;
res.status(201).json({ id: 1, name, email });
});
app.put('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `Updated user ${id}` });
});
app.delete('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `Deleted user ${id}` });
});
Route Parameters
javascript
// Path parameters
app.get('/users/:id', (req, res) => {
const { id } = req.params;
res.json({ userId: id });
});
// Multiple parameters
app.get('/users/:id/posts/:postId', (req, res) => {
const { id, postId } = req.params;
res.json({ userId: id, postId });
});
// Query parameters
app.get('/search', (req, res) => {
const { q, limit = 10, offset = 0 } = req.query;
res.json({ query: q, limit: Number(limit), offset: Number(offset) });
});
// Optional parameters
app.get('/posts/:year/:month?', (req, res) => {
const { year, month } = req.params;
res.json({ year, month: month || 'all' });
});
Router Module
javascript
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ users: [] });
});
router.get('/:id', (req, res) => {
res.json({ user: { id: req.params.id } });
});
router.post('/', (req, res) => {
res.status(201).json({ message: 'User created' });
});
module.exports = router;
// app.js
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);
Middleware
Built-in Middleware
javascript
const express = require('express');
const app = express();
// Parse JSON
app.use(express.json({ limit: '10mb' }));
// Parse URL-encoded data
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
Custom Middleware
javascript
// Logging middleware
const logger = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
};
// 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' }; // Mock user
next();
};
// Rate limiting middleware
const rateLimit = (maxRequests = 100, windowMs = 15 * 60 * 1000) => {
const requests = new Map();
return (req, res, next) => {
const ip = req.ip;
const now = Date.now();
const windowStart = now - windowMs;
if (!requests.has(ip)) {
requests.set(ip, []);
}
const ipRequests = requests.get(ip);
const recentRequests = ipRequests.filter(time => time > windowStart);
if (recentRequests.length >= maxRequests) {
return res.status(429).json({ error: 'Too many requests' });
}
recentRequests.push(now);
requests.set(ip, recentRequests);
next();
};
};
// Usage
app.use(logger);
app.use('/api', rateLimit(100, 15 * 60 * 1000));
app.use('/api/protected', authenticate);
Error Handling Middleware
javascript
// Error handling middleware (must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
if (err.type === 'validation') {
return res.status(400).json({ error: err.message });
}
if (err.type === 'unauthorized') {
return res.status(401).json({ error: 'Unauthorized' });
}
res.status(500).json({ error: 'Internal server error' });
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
Request/Response Handling
Request Object
javascript
app.post('/api/users', (req, res) => {
// Request body
const { name, email } = req.body;
// Query parameters
const { limit } = req.query;
// Route parameters
const { id } = req.params;
// Headers
const contentType = req.get('Content-Type');
const userAgent = req.get('User-Agent');
// Other useful properties
console.log('Method:', req.method);
console.log('URL:', req.url);
console.log('IP:', req.ip);
console.log('Protocol:', req.protocol);
console.log('Hostname:', req.hostname);
});
Response Object
javascript
app.get('/api/users/:id', (req, res) => {
const user = { id: 1, name: 'John', email: '
[email protected]' };
// JSON response
res.json(user);
// Status code with response
res.status(201).json({ message: 'Created' });
// Send different response types
res.send('Plain text');
res.sendFile(path.join(__dirname, 'file.pdf'));
// Set headers
res.set('Content-Type', 'application/json');
res.set('X-Custom-Header', 'value');
// Cookies
res.cookie('sessionId', '123456', { httpOnly: true });
// Redirect
res.redirect('/login');
res.redirect(301, '/new-url');
});
Request Validation
javascript
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');
}
if (!email || !email.includes('@')) {
errors.push('Valid email is required');
}
if (errors.length > 0) {
return res.status(400).json({ errors });
}
next();
};
app.post('/api/users', validateUser, (req, res) => {
// User data is valid
res.status(201).json({ message: 'User created' });
});
Error Handling
Custom Error Classes
javascript
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 NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401);
}
}
Error Handling Patterns
javascript
// Async error wrapper
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage with async routes
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('User not found');
}
res.json(user);
}));
// Global error handler
app.use((err, req, res, next) => {
// Log error
console.error(err);
// Operational errors
if (err.isOperational) {
return res.status(err.statusCode).json({
status: 'error',
message: err.message
});
}
// Programming errors
res.status(500).json({
status: 'error',
message: 'Something went wrong'
});
});
Database Integration
MongoDB with Mongoose
bash
npm install mongoose
javascript
const mongoose = require('mongoose');
// Connection
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// User 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);
// CRUD Operations
app.get('/api/users', async (req, res) => {
try {
const users = await User.find().select('-password');
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.post('/api/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.get('/api/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
PostgreSQL with Sequelize
bash
npm install sequelize pg pg-hstore
javascript
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres'
});
// User Model
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
});
// Sync database
sequelize.sync();
// CRUD with Sequelize
app.get('/api/users', async (req, res) => {
const users = await User.findAll();
res.json(users);
});
app.post('/api/users', async (req, res) => {
const user = await User.create(req.body);
res.status(201).json(user);
});
Authentication & Authorization
JWT Authentication
bash
npm install jsonwebtoken bcryptjs
javascript
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Register
app.post('/api/auth/register', async (req, res) => {
try {
const { name, email, password } = req.body;
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const user = await User.create({
name,
email,
password: hashedPassword
});
// Generate token
const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });
res.status(201).json({
message: 'User created successfully',
token,
user: { id: user.id, name: user.name, email: user.email }
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Login
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
// Find user
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });
res.json({
message: 'Login successful',
token,
user: { id: user.id, name: user.name, email: user.email }
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Auth middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, JWT_SECRET, async (err, decoded) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
try {
const user = await User.findById(decoded.userId);
req.user = user;
next();
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
});
};
// Protected route
app.get('/api/profile', authenticateToken, (req, res) => {
res.json(req.user);
});
Role-based Authorization
javascript
const authorize = (roles = []) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (roles.length && !roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
// Usage
app.get('/api/admin/users', authenticateToken, authorize(['admin']), (req, res) => {
// Admin only route
});
app.delete('/api/users/:id', authenticateToken, authorize(['admin', 'moderator']), (req
// Admin or moderator only
});
File Upload and Static Files
File Upload with Multer
bash
npm install multer
javascript
const multer = require('multer');
const path = require('path');
// Storage configuration
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));
}
});
// File filter
const fileFilter = (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});
// Single file upload
app.post('/api/upload', upload.single('avatar'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
filename: req.file.filename,
originalName: req.file.originalname,
size: req.file.size
});
});
// Multiple files
app.post('/api/upload-multiple', upload.array('photos', 5), (req, res) => {
res.json({
message: `${req.files.length} files uploaded`,
files: req.files.map(file => ({
filename: file.filename,
originalName: file.originalname
}))
});
});
// Serve static files
app.use('/uploads', express.static('uploads'));
Environment Configuration
Using dotenv
bash
npm install dotenv
.env
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=admin
DB_PASS=password
JWT_SECRET=your-jwt-secret-key
API_KEY=your-api-key
config.js
javascript
require('dotenv').config();
const config = {
env: process.env.NODE_ENV || 'development',
port: parseInt(process.env.PORT) || 3000,
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
name: process.env.DB_NAME || 'myapp',
user: process.env.DB_USER || 'admin',
password: process.env.DB_PASS || 'password'
},
jwt: {
secret: process.env.JWT_SECRET || 'default-secret',
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
},
apiKey: process.env.API_KEY
};
// Validate required config
if (!config.jwt.secret || config.jwt.secret === 'default-secret') {
console.error('JWT_SECRET must be set in environment variables');
process.exit(1);
}
module.exports = config;
app.js
javascript
const config = require('./config');
app.listen(config.port, () => {
console.log(`Server running on port ${config.port} in ${config.env} mode`);
});
Testing and Debugging
Testing with Jest and Supertest
bash
npm install -D jest supertest
tests/app.test.js
javascript
const request = require('supertest');
const app = require('../app');
describe('User API', () => {
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);
});
test('POST /api/users should create user', async () => {
const userData = {
name: 'John Doe',
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);
});
test('POST /api/users with invalid data should return 400', async () => {
const response = await request(app)
.post('/api/users')
.send({})
.expect(400);
expect(response.body.error).toBeDefined();
});
});
package.json
json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
},
"jest": {
"testEnvironment": "node"
}
}
Debugging
javascript
// Debug middleware
const debug = require('debug')('app:server');
app.use((req, res, next) => {
debug(`${req.method} ${req.url}`);
next();
});
// VS Code debugging configuration
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/app.js",
"env": {
"NODE_ENV": "development",
"DEBUG": "app:*"
}
}
]
}
Complete Example Application
app.js
javascript
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
require('dotenv').config();
const app = express();
// Security middleware
app.use(helmet());
app.use(cors());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/users', require('./routes/users'));
app.use('/api/posts', require('./routes/posts'));
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something broke!' });
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
module.exports = app;
This guide covers the essential Express.js concepts used in production applications. Each section builds
upon the previous ones to create a complete backend application.