Express.js Framework
Building servers with raw Node.js works, but it is verbose and repetitive. Express.js simplifies everything - routing, middleware, error handling, and more. It is the most popular Node.js framework for good reason.
Why Express.js?
Compare the raw Node.js approach to Express:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/users') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ users: [] }));
}
});
server.listen(3000);
const express = require('express');
const app = express();
app.get('/users', (req, res) => {
res.json({ users: [] });
});
app.listen(3000);
Express Benefits
- Cleaner Routing - app.get(), app.post() instead of if/else chains
- Built-in JSON - res.json() automatically stringifies and sets headers
- Middleware - Easily add functionality like logging, auth, parsing
- Request Parsing - Easy access to params, query strings, body
- Error Handling - Centralized error handling
Getting Started with Express
# Create project folder
mkdir express-app
cd express-app
# Initialize npm project
npm init -y
# Install Express
npm install express
# Create main file
touch app.js
// Import Express
const express = require('express');
// Create Express application instance
const app = express();
// Define a route for GET requests to root URL
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
// Start server on port 3000
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Routing in Express
Express provides methods for each HTTP verb. The pattern is: app.METHOD(PATH, HANDLER)
// GET request to /
app.get('/', (req, res) => {
res.send('Home page');
});
// POST request to /users
app.post('/users', (req, res) => {
res.send('Create user');
});
// PUT request to /users/:id
app.put('/users/:id', (req, res) => {
res.send(`Update user ${req.params.id}`);
});
// PATCH request to /users/:id
app.patch('/users/:id', (req, res) => {
res.send(`Partial update user ${req.params.id}`);
});
// DELETE request to /users/:id
app.delete('/users/:id', (req, res) => {
res.send(`Delete user ${req.params.id}`);
});
// Handle ALL HTTP methods
app.all('/secret', (req, res) => {
res.send('Accessing secret area');
});
Route Parameters
Capture dynamic values from URLs using :paramName syntax:
// Single parameter
// URL: /users/123 -> req.params.id = "123"
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID: ${userId}`);
});
// Multiple parameters
// URL: /users/123/posts/456
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
// Optional parameter (use regex or check for undefined)
app.get('/products/:category?', (req, res) => {
if (req.params.category) {
res.send(`Products in ${req.params.category}`);
} else {
res.send('All products');
}
});
Query Strings
Access URL query parameters with req.query:
// URL: /search?q=nodejs&limit=10&page=2
app.get('/search', (req, res) => {
const { q, limit, page } = req.query;
console.log(q); // "nodejs"
console.log(limit); // "10" (always strings!)
console.log(page); // "2"
// Convert to numbers if needed
const limitNum = parseInt(limit) || 10;
const pageNum = parseInt(page) || 1;
res.json({
query: q,
limit: limitNum,
page: pageNum
});
});
Response Methods
Express provides many ways to send responses:
// Send plain text
res.send('Hello World');
// Send JSON (automatically sets Content-Type)
res.json({ message: 'Success', data: users });
// Send with specific status code
res.status(201).json({ id: 1, name: 'Created' });
res.status(404).json({ error: 'Not found' });
res.status(500).json({ error: 'Server error' });
// Send file
res.sendFile('/path/to/file.html');
// Download file
res.download('/path/to/file.pdf');
// Redirect to another URL
res.redirect('/new-url');
res.redirect(301, '/permanent-new-url');
// End response with no body
res.status(204).end();
// Set headers
res.set('X-Custom-Header', 'value');
res.set({
'Content-Type': 'text/plain',
'X-Another': 'header'
});
Middleware - The Heart of Express
Middleware functions have access to the request, response, and the next middleware function. They can execute code, modify req/res, end the request, or call next().
┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────┐
│ Request │────►│ Middleware 1 │────►│ Middleware 2 │────►│ Route Handler │────►│ Response │
└─────────┘ └──────┬───────┘ └──────┬───────┘ └───────┬───────┘ └──────────┘
│ │ │
next() next() res.send()
const express = require('express');
const app = express();
// Custom logging middleware (runs on EVERY request)
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next(); // MUST call next() to continue to next middleware
});
// Built-in middleware: Parse JSON bodies
app.use(express.json());
// Built-in middleware: Parse URL-encoded bodies (forms)
app.use(express.urlencoded({ extended: true }));
// Built-in middleware: Serve static files
app.use(express.static('public'));
// Middleware for specific path only
app.use('/api', (req, res, next) => {
console.log('API request received');
next();
});
// Your routes come AFTER middleware
app.get('/', (req, res) => {
res.send('Home');
});
app.listen(3000);
Order matters! Middleware runs in the order it is defined. Always put express.json() BEFORE your routes, or req.body will be undefined. Forgetting to call next() will hang the request.
Complete Express CRUD API
const express = require('express');
const app = express();
// Middleware
app.use(express.json());
// In-memory database
let books = [
{ id: 1, title: 'Node.js Basics', author: 'John Doe' },
{ id: 2, title: 'Express Guide', author: 'Jane Smith' }
];
let nextId = 3;
// GET all books
app.get('/api/books', (req, res) => {
res.json(books);
});
// GET single book
app.get('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === parseInt(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
// POST create book
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
// Validation
if (!title || !author) {
return res.status(400).json({
error: 'Title and author are required'
});
}
const newBook = {
id: nextId++,
title,
author
};
books.push(newBook);
res.status(201).json(newBook);
});
// PUT update book (replace)
app.put('/api/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = books.findIndex(b => b.id === id);
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
const { title, author } = req.body;
if (!title || !author) {
return res.status(400).json({
error: 'Title and author are required'
});
}
books[index] = { id, title, author };
res.json(books[index]);
});
// PATCH update book (partial)
app.patch('/api/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const book = books.find(b => b.id === id);
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
// Update only provided fields
if (req.body.title) book.title = req.body.title;
if (req.body.author) book.author = req.body.author;
res.json(book);
});
// DELETE book
app.delete('/api/books/:id', (req, res) => {
const id = parseInt(req.params.id);
const index = books.findIndex(b => b.id === id);
if (index === -1) {
return res.status(404).json({ error: 'Book not found' });
}
books.splice(index, 1);
res.status(204).end();
});
// 404 handler for unmatched routes
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Video Resources
Knowledge Check: Day 4
1. What does app.use(express.json()) do?
2. How do you access a URL parameter like /users/123?
3. What happens if you forget to call next() in middleware?
Task Manager API
Build an Express API for managing tasks:
GET /api/tasks- List all tasks (support ?completed=true filter)GET /api/tasks/:id- Get single taskPOST /api/tasks- Create task (title required, completed defaults false)PATCH /api/tasks/:id- Update task (toggle completed, change title)DELETE /api/tasks/:id- Delete task- Add request logging middleware
Day 4 Complete!
You have learned Express.js - the most popular Node.js framework. You understand routing, middleware, request/response handling, and how to build a complete CRUD API with much cleaner code than raw Node.js.
Tomorrow, we will organize our code better by learning about project structure, routers, and controllers - essential patterns for building maintainable APIs.