Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Express.js Basics
In the previous chapters, you learned how to create a basic HTTP server using Node’s built-in http module. While powerful, it requires significant boilerplate code for real-world applications. This is where Express.js comes in.
The Problem with Raw HTTP
Using the built-in http module, even simple tasks become verbose:
// Without Express - parsing JSON body manually
const http = require('http');
http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/users') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
const user = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user));
});
}
}).listen(3000);
This gets even more complex when you add routing, error handling, authentication, and other features.
Why Express.js?
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 for Node.js web development.
What Express Solves
| Problem | Express Solution |
|---|
| Complex routing logic | Simple, declarative routing |
| Parsing request bodies | Built-in middleware |
| Managing middleware | Composable middleware chain |
| Error handling | Centralized error handlers |
| Serving static files | One-line configuration |
Express by the Numbers
- 29+ million weekly downloads on NPM
- Powers thousands of production applications
- Extensive ecosystem of middleware packages
- Battle-tested since 2010
Installation
Creating a Basic Server
const express = require('express');
const app = express();
const PORT = 5000;
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Routing
Express makes routing much easier than the raw http module.
// GET request
app.get('/about', (req, res) => {
res.send('About Page');
});
// POST request
app.post('/contact', (req, res) => {
res.send('Contact Form Submitted');
});
// Dynamic Parameters
app.get('/users/:id', (req, res) => {
res.send(`User ID: ${req.params.id}`);
});
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 (next).
The Airport Security Analogy: Think of middleware like the checkpoints you pass through at an airport. Each checkpoint (middleware function) inspects something specific: one checks your ticket (authentication), another scans your bag (body parsing), another checks the no-fly list (authorization). Each checkpoint either lets you through to the next one (next()), or stops you and sends you back (res.status(403).send()). The order of checkpoints matters—you cannot board (reach the route handler) without passing through all of them first. This is exactly why the order in which you register middleware with app.use() is critical.
Creating Custom Middleware
const logger = (req, res, next) => {
console.log(`${req.method} ${req.protocol}://${req.get('host')}${req.originalUrl}`);
next(); // CRITICAL: Call next() to pass control to the next middleware.
// If you forget next(), the request hangs forever -- the client
// waits for a response that never comes. This is the #1 Express
// middleware bug for beginners.
};
// Initialize middleware -- this runs for EVERY request to the server
app.use(logger);
Built-in Middleware
Express has built-in middleware for parsing body data.
// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies (forms)
app.use(express.urlencoded({ extended: false }));
Serving Static Files
To serve static files such as images, CSS files, and JavaScript files, use the express.static built-in middleware function.
const path = require('path');
// Set 'public' folder as static folder
app.use(express.static(path.join(__dirname, 'public')));
Summary
- Express.js simplifies server creation and routing—it is a thin wrapper around Node’s
http module
- Use
app.get(), app.post(), etc., for routing
- Middleware executes code between request and response—order of
app.use() calls matters
- Always call
next() in middleware unless you are sending a response
- Error-handling middleware must have exactly 4 parameters
(err, req, res, next) and must be registered last
express.json() parses JSON request bodies
express.static() serves static assets
Express Router
Organize routes into separate modules:
routes/users.js
const express = require('express');
const router = express.Router();
// All routes here are prefixed with /api/users
router.get('/', (req, res) => {
res.json([{ id: 1, name: 'John' }]);
});
router.get('/:id', (req, res) => {
res.json({ id: req.params.id, name: 'John' });
});
router.post('/', (req, res) => {
res.status(201).json({ message: 'User created' });
});
module.exports = router;
app.js
const express = require('express');
const userRoutes = require('./routes/users');
const app = express();
app.use(express.json());
app.use('/api/users', userRoutes);
app.listen(3000);
Error Handling Middleware
// Custom error class
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
// Route that throws error
app.get('/error', (req, res, next) => {
next(new AppError('Something went wrong', 500));
});
// Async error wrapper
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// Usage with async routes
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.find(); // If this throws, error handler catches it
res.json(users);
}));
// Error handling middleware -- MUST have exactly 4 parameters (err, req, res, next).
// Express identifies error handlers by their arity (parameter count).
// If you accidentally omit one parameter, Express treats it as normal middleware
// and your errors will not be caught. This is a subtle but common bug.
// This middleware MUST be registered AFTER all routes.
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Internal Server Error';
res.status(statusCode).json({
success: false,
error: message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
Request Object Deep Dive
app.post('/api/data', (req, res) => {
// URL parameters (/users/:id)
console.log(req.params.id);
// Query string (/users?sort=name&order=desc)
console.log(req.query.sort); // 'name'
console.log(req.query.order); // 'desc'
// Request body (POST/PUT data)
console.log(req.body);
// Headers
console.log(req.headers['content-type']);
console.log(req.get('Authorization'));
// Cookies (requires cookie-parser)
console.log(req.cookies.session);
// Full URL info
console.log(req.originalUrl); // /api/data?sort=name
console.log(req.path); // /api/data
console.log(req.hostname); // localhost
console.log(req.ip); // Client IP
console.log(req.protocol); // http or https
console.log(req.method); // POST
});
Response Object Methods
app.get('/response-examples', (req, res) => {
// Send string
res.send('Hello World');
// Send JSON
res.json({ message: 'Hello' });
// Send with status
res.status(201).json({ created: true });
// Redirect
res.redirect('/other-route');
res.redirect(301, '/permanent-redirect');
// Render template (with template engine)
res.render('index', { title: 'Home' });
// Download file
res.download('./file.pdf');
// Send file
res.sendFile('/absolute/path/to/file.html');
// Set headers
res.set('Content-Type', 'text/html');
res.set({
'X-Custom-Header': 'value',
'Cache-Control': 'no-cache'
});
// Set cookie
res.cookie('session', 'abc123', {
httpOnly: true,
secure: true,
maxAge: 3600000
});
// Clear cookie
res.clearCookie('session');
});
Third-Party Middleware
const express = require('express');
const helmet = require('helmet'); // Security headers
const cors = require('cors'); // Cross-origin requests
const morgan = require('morgan'); // Logging
const compression = require('compression'); // Gzip
const rateLimit = require('express-rate-limit'); // Rate limiting
const app = express();
// IMPORTANT: Middleware order matters! A common production setup applies
// security middleware first, then logging, then body parsing, then routes.
// If you put body parsing AFTER your routes, req.body will be undefined.
// Security headers
app.use(helmet());
// CORS
app.use(cors({
origin: 'https://example.com',
methods: ['GET', 'POST'],
credentials: true
}));
// Logging
app.use(morgan('dev')); // or 'combined' for production
// Compression
app.use(compression());
// 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 parsers
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
Application Structure Best Practice
project/
├── src/
│ ├── config/
│ │ └── db.js
│ ├── controllers/
│ │ └── userController.js
│ ├── middleware/
│ │ ├── auth.js
│ │ └── errorHandler.js
│ ├── models/
│ │ └── User.js
│ ├── routes/
│ │ ├── index.js
│ │ └── userRoutes.js
│ ├── services/
│ │ └── userService.js
│ ├── utils/
│ │ └── helpers.js
│ └── app.js
├── tests/
├── .env
├── .gitignore
├── package.json
└── server.js