ExpressJS - Cheatsheet
Modern Express.js Cheatsheet
Section titled “Modern Express.js Cheatsheet”In this cheatsheet, we will cover the essential features and concepts of Express.js for building robust and scalable applications in 2024. We’ll explore routing, middleware, modern asynchronous patterns, and data handling. You’ll learn how to set up a project with a modern toolchain, handle various HTTP methods, manage static assets, and integrate with databases using best practices. This guide is your quick reference for building efficient APIs and web applications with Express.js.
Table of Contents
Section titled “Table of Contents”- Introduction
- Modern Project Setup
- Core Concepts: Routing
- Core Concepts: Middleware
- Serving Content: Views & Static Files
- The Request and Response Objects
- Modular Routing with
express.Router - Application Methods
- Modern Error Handling
- Debugging and Logging
- Database Integration with Async/Await
- Secure Authentication with JWT
- Essential Security Practices
- Testing with Modern Tooling
- Deployment to the Cloud
Introduction
Section titled “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 acts as a thin layer of fundamental web application features, without obscuring Node.js features that you know and love.
Modern Project Setup
Section titled “Modern Project Setup”1. Initialize Your Project
Section titled “1. Initialize Your Project”Create a new directory for your project, navigate into it, and initialize a package.json file. This file will track your project’s metadata and dependencies.
$ mkdir my-modern-app$ cd my-modern-app$ npm init -y2. Install Express
Section titled “2. Install Express”Install Express and add it to your project’s dependencies.
$ npm install express3. Create an Entry File (index.js)
Section titled “3. Create an Entry File (index.js)”This code sets up a basic Express server using modern ES Module syntax. It listens on a port defined by an environment variable (a best practice) and responds with a JSON message. To use ES Module syntax (import/export), add "type": "module" to your package.json.
import express from 'express';
// Best practice: use environment variables for configurationconst PORT = process.env.PORT || 3000;
const app = express();
app.get('/', (req, res) => { res.json({ message: 'Hello Modern World!' });});
app.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`);});4. Run the Application
Section titled “4. Run the Application”For development, it’s highly recommended to use a tool like nodemon that automatically restarts your server on file changes.
# Install nodemon as a development dependency$ npm install --save-dev nodemon
# Add a script to your package.json# "scripts": {# "dev": "nodemon index.js"# }
# Run the dev server$ npm run devCore Concepts: Routing
Section titled “Core Concepts: Routing”Routing defines how the application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, etc.).
Basic Routes
Section titled “Basic Routes”Each route can have one or more handler functions, which are executed when the route is matched.
// GET method routeapp.get('/', (req, res) => res.send('GET request to the homepage'));
// POST method routeapp.post('/users', (req, res) => res.send('POST request to create a user'));
// Dynamic route with a parameterapp.get('/users/:userId', (req, res) => { res.send(`Details for user ${req.params.userId}`);});Route Handlers
Section titled “Route Handlers”You can provide multiple callback functions that behave like middleware to handle a request. The only exception is that these callbacks may invoke next('route') to bypass the remaining route callbacks.
const logTimestamp = (req, res, next) => { console.log('Time: ', Date.now()); next();};
app.get('/with-handler', [logTimestamp], (req, res) => { res.send('Check the console for a timestamp!');});Core Concepts: Middleware
Section titled “Core Concepts: Middleware”Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle. They can execute any code, make changes to the request and the response objects, end the request-response cycle, and call the next middleware in the stack.
Common Built-in Middleware
Section titled “Common Built-in Middleware”Express has built-in middleware for common tasks. Since Express 4.16+, the popular body-parser is included.
// Middleware to parse JSON bodiesapp.use(express.json());
// Middleware to parse URL-encoded bodiesapp.use(express.urlencoded({ extended: true }));
// Middleware to serve static files from the 'public' directoryapp.use(express.static('public'));Custom Middleware
Section titled “Custom Middleware”Writing your own middleware is simple. Here is a logger that logs the request method and path.
const requestLogger = (req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next(); // Pass control to the next handler};
app.use(requestLogger);Serving Content: Views & Static Files
Section titled “Serving Content: Views & Static Files”While many modern Express apps serve as a JSON API for a frontend client (like React or Vue), it can also render dynamic HTML on the server using template engines.
1. Install and Set a View Engine
Section titled “1. Install and Set a View Engine”Pug, EJS, and Handlebars are popular choices. Here’s how to set up EJS.
$ npm install ejs
// In your app.jsapp.set('view engine', 'ejs');// Optional: specify the directory where templates are located// app.set('views', path.join(__dirname, 'views'));2. Create a Template (e.g., views/profile.ejs)
Section titled “2. Create a Template (e.g., views/profile.ejs)”Templates can embed dynamic data passed from your route handler.
<!DOCTYPE html><html><head> <title><%= title %></title></head><body> <h1>Hello, <%= user.name %>!</h1></body></html>3. Render the Template
Section titled “3. Render the Template”Use res.render() to render a view and send the rendered HTML string to the client.
app.get('/profile', (req, res) => { const userData = { name: 'Alex' }; res.render('profile', { title: 'User Profile', user: userData });});The Request and Response Objects
Section titled “The Request and Response Objects”Route handlers and middleware get access to req and res objects, which represent the HTTP request and response.
Request Object (req) Properties
Section titled “Request Object (req) Properties”| Property | Explanation |
|---|---|
req.params | Route parameters (e.g., /users/:id) |
req.query | Query string parameters (e.g., ?sort=desc) |
req.body | Parsed request body (requires express.json() or express.urlencoded()) |
req.headers | Incoming request headers |
req.method | HTTP method of the request (GET, POST, etc.) |
req.originalUrl | Full original URL of the request |
Response Object (res) Methods
Section titled “Response Object (res) Methods”| Method | Explanation |
|---|---|
res.send() | Send a response of various types (String, Buffer, object) |
res.json() | Send a JSON response (automatically sets Content-Type) |
res.status() | Set the HTTP status code for the response |
res.render() | Render a view template |
res.redirect() | Redirect to a specified URL |
res.cookie() | Set a cookie |
Example: Chaining Status and JSON
Section titled “Example: Chaining Status and JSON”app.get('/api/health', (req, res) => { res.status(200).json({ status: 'UP' });});Modular Routing with express.Router
Section titled “Modular Routing with express.Router”For larger applications, it’s best practice to use express.Router to group route handlers in separate files, keeping your main application file clean.
1. Create a Router File (e.g., routes/userRoutes.js)
Section titled “1. Create a Router File (e.g., routes/userRoutes.js)”import { Router } from 'express';
const router = Router();
// This route is now effectively /api/users/router.get('/', (req, res) => { res.send('List of all users');});
// This route is now effectively /api/users/profilerouter.get('/profile', (req, res) => { res.send('User profile page');});
export default router;2. Mount the Router in Your Main App File
Section titled “2. Mount the Router in Your Main App File”import express from 'express';import userRoutes from './routes/userRoutes.js';
const app = express();
// Mount the router on the /api/users pathapp.use('/api/users', userRoutes);
// ... other app setup ...app.listen(3000);Application Methods
Section titled “Application Methods”| Method | Explanation |
|---|---|
app.use([path], callback) | Mount middleware functions at a specified path |
app.get(path, callback) | Handle GET requests |
app.post(path, callback) | Handle POST requests |
app.put(path, callback) | Handle PUT requests |
app.delete(path, callback) | Handle DELETE requests |
app.listen(port, [callback]) | Bind the application to a port and start the server |
app.set(name, value) | Assigns setting name to value |
app.engine(ext, callback) | Register the given template engine callback as ext |
Modern Error Handling
Section titled “Modern Error Handling”A centralized error-handling middleware is a best practice. It should be defined last, after all other app.use() and routes calls. This special middleware has four arguments (err, req, res, next).
// 404 handler for unmatched routesapp.use((req, res, next) => { res.status(404).send('Sorry, that route does not exist.');});
// Centralized error handlerapp.use((err, req, res, next) => { console.error(err.stack); // Avoid leaking stack trace in production const statusCode = err.statusCode || 500; const message = process.env.NODE_ENV === 'production' ? 'Something broke!' : err.message; res.status(statusCode).json({ error: message });});Debugging and Logging
Section titled “Debugging and Logging”Effective debugging and logging are crucial for development and production.
Using the Node.js Inspector
Section titled “Using the Node.js Inspector”Run your app with the --inspect flag to enable debugging with tools like Chrome DevTools or the VS Code debugger. This allows you to set breakpoints and inspect variables in real-time.
$ node --inspect index.jsStructured Logging
Section titled “Structured Logging”For production, simple console.log is not enough. Use a structured logging library like pino or winston for filterable, machine-readable logs.
$ npm install pino
// Example usageimport pino from 'pino';const logger = pino({ level: 'info' });
logger.info('Server started successfully.');logger.error({ err }, 'An error occurred during request processing.');Database Integration with Async/Await
Section titled “Database Integration with Async/Await”Modern Node.js heavily favors async/await for handling asynchronous operations like database queries. This makes code cleaner and easier to reason about than callbacks or .then() chains.
PostgreSQL with pg
Section titled “PostgreSQL with pg”The pg library is the standard for connecting to PostgreSQL. It fully supports Promises, making it perfect for async/await.
$ npm install pg
// db.jsimport pg from 'pg';const { Pool } = pg;
const pool = new Pool({ connectionString: process.env.DATABASE_URL, // e.g., postgresql://user:password@host:port/database});
export default pool;
// usage in a routeimport pool from './db.js';
app.get('/users', async (req, res, next) => { try { const { rows } = await pool.query('SELECT * FROM users'); res.json(rows); } catch (err) { next(err); // Pass error to central handler }});MongoDB with mongodb driver
Section titled “MongoDB with mongodb driver”The native mongodb driver is fully async-first.
$ npm install mongodb
// mongo.jsimport { MongoClient } from 'mongodb';
const uri = process.env.MONGO_URI; // e.g., mongodb://localhost:27017const client = new MongoClient(uri);
let db;
async function connectToDatabase() { try { await client.connect(); db = client.db('my-app-db'); console.log('Connected to MongoDB'); } catch (err) { console.error('Failed to connect to MongoDB', err); process.exit(1); }}
export { connectToDatabase, db };
// usage in a routeimport { db } from './mongo.js';
app.get('/products', async (req, res, next) => { try { const products = await db.collection('products').find({}).toArray(); res.json(products); } catch (err) { next(err); }});Secure Authentication with JWT
Section titled “Secure Authentication with JWT”JSON Web Tokens (JWT) are a standard way to handle stateless authentication in APIs.
1. Install Dependencies
Section titled “1. Install Dependencies”# JWT for token creation, bcrypt for hashing passwords$ npm install jsonwebtoken bcryptjs2. User Registration and Login (Controller Logic)
Section titled “2. User Registration and Login (Controller Logic)”This logic uses async/await for clean, readable code and try/catch for error handling. Crucially, secrets like JWT_SECRET must be stored in environment variables, not hardcoded.
import bcrypt from 'bcryptjs';import jwt from 'jsonwebtoken';// Assume User model from a database ORM like Mongoose or Prismaimport User from '../models/User.js';
export const register = async (req, res, next) => { try { const { email, password, name } = req.body; const hashedPassword = await bcrypt.hash(password, 12);
const newUser = new User({ email, name, password: hashedPassword }); await newUser.save();
res.status(201).json({ message: 'User created!' }); } catch (err) { next(err); }};
export const login = async (req, res, next) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user) { return res.status(401).json({ message: 'Authentication failed' }); }
const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ message: 'Authentication failed' }); }
const token = jwt.sign( { id: user._id, email: user.email }, process.env.JWT_SECRET, // Use environment variable! { expiresIn: '1h' } );
res.status(200).json({ token }); } catch (err) { next(err); }};3. Authentication Middleware
Section titled “3. Authentication Middleware”This middleware protects routes by verifying the JWT from the Authorization header.
import jwt from 'jsonwebtoken';
export const protect = (req, res, next) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ message: 'No token, authorization denied' }); }
const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; // Add decoded user payload to the request next(); } catch (err) { res.status(401).json({ message: 'Token is not valid' }); }};
// Usage:// app.get('/api/protected-route', protect, (req, res) => { ... });Essential Security Practices
Section titled “Essential Security Practices”Security is not an afterthought. Start with these essential middleware and practices.
Use Helmet for Secure Headers
Section titled “Use Helmet for Secure Headers”helmet sets various HTTP headers to help protect your app from well-known web vulnerabilities.
$ npm install helmet
// In app.jsimport helmet from 'helmet';app.use(helmet());Rate Limiting to Prevent Abuse
Section titled “Rate Limiting to Prevent Abuse”Use express-rate-limit to prevent brute-force attacks and denial-of-service.
$ npm install express-rate-limit
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per window standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers});
// Apply the rate limiting middleware to all requestsapp.use(limiter);CSRF Protection for Session-Based Apps
Section titled “CSRF Protection for Session-Based Apps”Cross-Site Request Forgery (CSRF) is a concern for traditional web apps using cookies/sessions. For stateless APIs using JWT in headers, CSRF is less of a risk. If you are building a server-rendered app with sessions, a library like csurf’s successor or a pattern like double-submit cookies is necessary. For modern SPAs, ensure you are using SameSite cookies and proper CORS policies.
Testing with Modern Tooling
Section titled “Testing with Modern Tooling”A combination of a test runner (like Jest or Vitest), and an HTTP assertion library (Supertest) is a powerful setup for testing Express applications.
1. Install Dependencies
Section titled “1. Install Dependencies”We’ll use Vitest for its speed and modern features, and Supertest for making requests to our app.
$ npm install --save-dev vitest supertest2. Structure Your App for Testing
Section titled “2. Structure Your App for Testing”Separate your app creation from the server listening part. This allows you to import the app object into your tests without starting the server.
import express from 'express';const app = express();app.get('/', (req, res) => res.status(200).send('OK'));export default app;
// index.js (or server.js)import app from './app.js';const PORT = 3000;app.listen(PORT, () => console.log(`Listening on port ${PORT}`));3. Write an Integration Test (app.test.js)
Section titled “3. Write an Integration Test (app.test.js)”This test uses supertest to make a real HTTP request to the in-memory app and vitest to define the test suite and assertions.
import { describe, it, expect } from 'vitest';import request from 'supertest';import app from '../app.js'; // Import the express app
describe('GET /', () => { it('should respond with a 200 status code and OK text', async () => { const response = await request(app).get('/'); expect(response.statusCode).toBe(200); expect(response.text).toBe('OK'); });});
describe('GET /non-existent-route', () => { it('should respond with a 404 status code', async () => { const response = await request(app).get('/not-a-real-route'); expect(response.statusCode).toBe(404); });});Deployment to the Cloud
Section titled “Deployment to the Cloud”Platforms like Vercel, Render, and Heroku make deploying Node.js applications straightforward. Here’s a quick guide for Vercel, which excels at serverless deployments.
1. Structure for Vercel
Section titled “1. Structure for Vercel”Vercel can automatically convert your Express app into a serverless function. Place your main Express app file inside an /api directory.
my-modern-app/├── api/│ └── index.js <-- Your Express app code goes here├── public/ <-- Static files (optional)└── package.json2. Update Your Express App (api/index.js)
Section titled “2. Update Your Express App (api/index.js)”Ensure your app exports the app object. Vercel’s build process will handle the server creation.
import express from 'express';const app = express();
app.get('/api/hello', (req, res) => { res.send('Hello from Express on Vercel!');});
// Export the appexport default app;3. Add vercel.json for Routing
Section titled “3. Add vercel.json for Routing”This configuration file tells Vercel to direct all incoming requests to your serverless Express app.
{ "version": 2, "rewrites": [ { "source": "/api/(.*)", "destination": "/api" } ]}4. Deploy
Section titled “4. Deploy”Install the Vercel CLI, link your project, and deploy.
# Install Vercel CLI globally$ npm install -g vercel
# Deploy from your project root$ vercel
# To deploy to production$ vercel --prod