Difficulty: Beginner
Express is a minimal, unopinionated web framework for Node.js. It is the most popular Node.js framework by a wide margin, powering millions of applications worldwide. Express provides a thin layer of fundamental web application features on top of Node.js, without obscuring the Node.js features that developers already know and love.
The key reason developers choose Express over raw Node.js HTTP servers is productivity. As you saw with the built-in http module, handling routes, parsing request bodies, serving static files, and managing middleware all require significant boilerplate code. Express provides clean, declarative APIs for all of these tasks. What takes 30 lines with raw Node.js often takes 3 lines with Express.
Express follows the middleware pattern: every request passes through a chain of functions (middleware) that can read the request, modify the response, or pass control to the next function in the chain. This is the core architectural concept that makes Express so flexible. Routing, body parsing, authentication, logging, error handling - they are all implemented as middleware.
To get started with Express, you initialize a Node.js project with 'npm init', install Express with 'npm install express', and create your application file. The express() function creates an application instance (conventionally named 'app') that you configure with routes and middleware. Finally, app.listen() starts the server. Express sits on top of Node.js HTTP - under the hood, app.listen() calls http.createServer() with your Express app as the request handler.
Express is described as 'unopinionated' because it does not enforce any particular project structure, database choice, template engine, or architectural pattern. You can organize your code however you want, use any database, and choose from dozens of template engines and middleware packages. This flexibility is both a strength (maximum freedom) and a challenge (you must make more architectural decisions yourself).
const express = require('express');
const app = express();
const PORT = 3000;
// Define a route for GET /
app.get('/', (req, res) => {
res.send('Hello, World!');
});
// Define a route for GET /api/status
app.get('/api/status', (req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
// Start the server
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
express() creates the app. app.get() defines a route handler for GET requests. res.send() automatically sets Content-Type based on the argument type. res.json() sends a JSON response. app.listen() starts the server on the specified port.
// --- Raw Node.js (verbose) ---
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/api/users') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify([{ id: 1, name: 'Alice' }]));
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not Found' }));
}
});
server.listen(3000);
// --- Express (clean) ---
const express = require('express');
const app = express();
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
app.listen(3000);
The Express version is significantly shorter and more readable. Express handles Content-Type headers, JSON serialization, 404 responses, and status codes automatically. The declarative routing with app.get() is clearer than if/else chains.
const express = require('express');
const app = express();
// Parse JSON request bodies
app.use(express.json());
let tasks = [
{ id: 1, title: 'Learn Express', done: false }
];
// GET all tasks
app.get('/api/tasks', (req, res) => {
res.json(tasks);
});
// GET single task
app.get('/api/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === parseInt(req.params.id));
if (!task) return res.status(404).json({ error: 'Task not found' });
res.json(task);
});
// POST create task
app.post('/api/tasks', (req, res) => {
const task = {
id: tasks.length + 1,
title: req.body.title,
done: false
};
tasks.push(task);
res.status(201).json(task);
});
// DELETE task
app.delete('/api/tasks/:id', (req, res) => {
tasks = tasks.filter(t => t.id !== parseInt(req.params.id));
res.json({ message: 'Task deleted' });
});
app.listen(3000, () => console.log('Task API running on port 3000'));
This shows a complete CRUD API in Express. app.use(express.json()) is middleware that parses JSON request bodies. Route parameters like :id are accessed via req.params.id. res.status() sets the HTTP status code, and it is chainable with .json().
Express.js, Web Framework, npm install, app.listen, app.get, Hello World