Node.js 28 🟢 Introduction to Express.js Framework
Express is a minimal, flexible web application framework for Node.js. It provides a robust set of features for building web and mobile applications, but it does so without hiding the Node.js features you already know and use. The previous chapters covered the raw http and https modules—the primitives that Express is built on. This chapter introduces the framework that sits above them: what it provides, how it is installed, and the shape of an Express application.
Key point: Express is a thin layer over node:http. It adds routing, middleware, and convenience methods for the request and response objects, but it does not replace the underlying primitives. The req and res objects in an Express handler are the same objects that node:http provides, with additional methods attached. The framework’s philosophy is to provide small, robust tooling for HTTP servers without forcing a specific ORM, template engine, or project structure.
Why Express exists
The routing problem. The raw http module gives you a request and a response, but no router. Every path must be matched manually with if statements or a switch. Express provides app.get(), app.post(), app.put(), and app.delete() methods that map HTTP methods and paths to handler functions. The matching is built in, and the handlers are called only when the method and path match.
The middleware problem. A web application has concerns that apply to many routes: parsing the request body, setting CORS headers, authenticating the user, logging the request, handling errors. The raw http module requires each handler to repeat these concerns. Express introduces middleware—functions that run before the handler, receive the request and response, and either end the response or pass control to the next function. Middleware can be applied globally, to a path prefix, or to a specific route.
The boilerplate problem. The raw http module requires manual parsing of the URL, manual setting of the Content-Type header, and manual framing of the response body. Express provides res.send(), res.json(), res.status(), and res.redirect() methods that handle these details. The handler focuses on the logic; the framework handles the HTTP mechanics.
The ecosystem problem. Express is the most widely used Node.js web framework. It has a large ecosystem of middleware modules for common tasks: body-parser, cors, helmet, morgan, cookie-parser, and dozens of others. The framework’s minimal core and its middleware interface mean that the ecosystem provides the features, not the framework itself.
The flexibility problem. Express does not force a project structure. It does not require a specific template engine. It does not include an ORM. It supports over 14 template engines through the consolidate.js library, and it works with any database library. The framework provides the HTTP layer; the application provides everything else.
a. Installing Express and creating an application
Express is installed with npm. The package requires Node.js 18 or higher.
mkdir myapp
cd myapp
npm init -y
npm install express
The npm init -y command creates a package.json file with default values. The npm install express command downloads Express and adds it to the dependencies in package.json.
The simplest Express application is a single file. It creates an app, defines a route, and listens on a port.
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
The express() function returns an application object. The app.get() method defines a route for GET requests to the path /. The handler receives the request and response, and calls res.send() to send the response. The app.listen() method starts the server on the specified port.
Running the application:
node app.js
The server starts and listens on port 3000. A request to http://localhost:3000/ receives the string Hello World!. A request to any other path receives a 404 Not Found response, which Express handles automatically.
The express executable can generate a full application scaffold, but the single-file example is the starting point. The scaffold includes a directory structure, a template engine, and separate files for routes and configuration.
b. Routing: methods, paths, and parameters
Express routes are defined with methods named after HTTP verbs. Each method takes a path and one or more handler functions.
app.get('/', (req, res) => {
res.send('GET request to homepage');
});
app.post('/', (req, res) => {
res.send('POST request to homepage');
});
app.put('/user', (req, res) => {
res.send('PUT request to /user');
});
app.delete('/user', (req, res) => {
res.send('DELETE request to /user');
});
The app.all() method matches all HTTP methods for a path. It is useful for middleware that should run for every request to a path prefix.
app.all('/api/*', requireAuthentication);
The * is a wildcard that matches any suffix. The requireAuthentication middleware runs for every request to /api/ and any path under it.
Paths can include named parameters. The parameter is prefixed with a colon and is available in req.params.
app.get('/users/:userId', (req, res) => {
res.send(`User ID: ${req.params.userId}`);
});
A request to /users/42 sets req.params.userId to '42'. Multiple parameters can be included in a single path.
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params);
});
The req.params object contains both parameters. Path parameters are strings; they must be parsed if they represent numbers or other types.
Query string parameters are available in req.query.
app.get('/search', (req, res) => {
const { q, limit } = req.query;
res.send(`Searching for ${q} with limit ${limit}`);
});
A request to /search?q=express&limit=10 sets req.query.q to 'express' and req.query.limit to '10'.
The app.param() method maps logic to route parameters. It runs when a parameter with the specified name is present in the route path.
app.param('userId', (req, res, next, id) => {
User.find(id, (err, user) => {
if (err) return next(err);
if (!user) return next(new Error('User not found'));
req.user = user;
next();
});
});
The callback receives the parameter value and can perform validation or loading. The req.user is available in the route handler that uses the :userId parameter.
c. Middleware and the request-response cycle
Middleware functions are functions that have access to the request object, the response object, and the next function. They can execute code, modify the request and response, end the request-response cycle, or call next() to pass control to the next middleware.
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
app.use(logger);
The app.use() method applies middleware to all routes. The middleware runs for every request before the route handler. The next() call passes control to the next middleware or the route handler.
Middleware can be applied to a specific path prefix:
app.use('/api', logger);
This runs the logger for requests to /api and any path under it. Middleware can also be applied to a specific route:
app.get('/user', logger, (req, res) => {
res.send('User page');
});
The middleware runs before the handler. If the middleware does not call next(), the handler does not run, and the response is not sent unless the middleware sends it.
Middleware that takes four arguments is an error handler. It runs when an error is passed to next().
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
The error handler must be defined after all other middleware and routes. It receives the error as the first argument.
The Express application itself is a middleware function. It can be used inside another Express application or with app.use().
const subApp = express();
subApp.get('/', (req, res) => res.send('Sub app'));
app.use('/sub', subApp);
The subApp is mounted at /sub. A request to /sub/ is handled by the sub-application.
The request and response objects in Express are the same objects that node:http provides, with additional methods attached. The req is an http.IncomingMessage, and the res is an http.ServerResponse. The Express methods (res.send, res.json, res.status, etc.) are added by the framework. The underlying stream methods (req.pipe(), req.on('data'), res.write()) are still available.
Complete Example Session
// ============================================
// PART 1: SIMPLEST EXPRESS APP
// ============================================
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
// ============================================
// PART 2: ROUTING BY METHOD
// ============================================
app.get('/users', (req, res) => res.send('GET users'));
app.post('/users', (req, res) => res.send('POST users'));
app.put('/users/:id', (req, res) => res.send(`PUT user ${req.params.id}`));
app.delete('/users/:id', (req, res) => res.send(`DELETE user ${req.params.id}`));
// ============================================
// PART 3: PATH PARAMETERS
// ============================================
app.get('/users/:userId', (req, res) => {
res.send(`User ID: ${req.params.userId}`);
});
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params);
});
// ============================================
// PART 4: QUERY PARAMETERS
// ============================================
app.get('/search', (req, res) => {
const { q, limit } = req.query;
res.send(`Searching for ${q}, limit ${limit}`);
});
// ============================================
// PART 5: MIDDLEWARE
// ============================================
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next();
}
app.use(logger);
// ============================================
// PART 6: PATH-SPECIFIC MIDDLEWARE
// ============================================
app.use('/api', logger);
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
// ============================================
// PART 7: ROUTE-SPECIFIC MIDDLEWARE
// ============================================
app.get('/user', logger, (req, res) => {
res.send('User page');
});
// ============================================
// PART 8: ERROR HANDLING MIDDLEWARE
// ============================================
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// ============================================
// PART 9: MOUNTING A SUB-APPLICATION
// ============================================
const subApp = express();
subApp.get('/', (req, res) => res.send('Sub app'));
app.use('/sub', subApp);
// ============================================
// PART 10: RESPONSE METHODS
// ============================================
app.get('/json', (req, res) => {
res.json({ message: 'hello' });
});
app.get('/redirect', (req, res) => {
res.redirect('/');
});
app.get('/status', (req, res) => {
res.status(201).send('Created');
});
The ten parts covered the simplest app, routing by method, path parameters, query parameters, middleware, path-specific middleware, route-specific middleware, error handling, sub-applications, and response methods.
Quick Reference
Installation
| Command | Purpose |
|---|---|
npm init -y | Create package.json |
npm install express | Install Express |
node app.js | Run the app |
Routing Methods
| Method | HTTP Verb |
|---|---|
app.get() | GET |
app.post() | POST |
app.put() | PUT |
app.delete() | DELETE |
app.all() | All methods |
Request Properties
| Property | Value |
|---|---|
req.params | Path parameters |
req.query | Query string parameters |
req.body | Parsed body (with middleware) |
req.method | HTTP method |
req.url | Full URL |
req.path | Path without query |
req.headers | Request headers |
Response Methods
| Method | Purpose |
|---|---|
res.send() | Send string/buffer/object |
res.json() | Send JSON |
res.status() | Set status code |
res.redirect() | Redirect |
res.sendFile() | Send a file |
res.set() | Set a header |
res.end() | End the response |
Middleware
| Type | Signature | Purpose |
|---|---|---|
| Regular | (req, res, next) | Runs before handlers |
| Error | (err, req, res, next) | Handles errors |
app.use() | Global | All routes |
app.use('/path') | Path-scoped | Path prefix |
app.get('/path', mw) | Route-scoped | Specific route |
Best Practices
✅ Do This:
// Use express() to create the app
const app = express(); // ✅
// Define routes with HTTP method names
app.get('/users', handler); // ✅
// Use named parameters for path variables
app.get('/users/:id', handler); // ✅
// Use middleware for cross-cutting concerns
app.use(logger); // ✅
// Define error handlers last
app.use((err, req, res, next) => { }); // ✅
// Use res.json() for JSON responses
res.json({ ok: true }); // ✅
// Use express.json() for JSON body parsing
app.use(express.json()); // ✅
❌ Don’t Do This:
// Don't forget to call next() in middleware
function mw(req, res, next) { console.log('hi'); } // ❌ hangs
// Don't define error handlers before routes
app.use((err, req, res, next) => { }); // ⚠️
// Don't use res.send() after res.json()
res.json(data);
res.send('more'); // error // ❌
// Don't forget to install Express
const express = require('express'); // if not installed // ❌
// Don't hardcode the port
app.listen(3000); // use process.env.PORT // ⚠️
// Don't put all routes in one file for large apps
// Use express.Router() for modular routes // ⚠️
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Request hangs | Middleware did not call next() | Call next() |
| Body is undefined | No body parser | app.use(express.json()) |
| 404 for defined route | Wrong method or path | Check method and path |
| Error handler not called | Defined before routes | Move to the end |
req.params empty | Parameter not in path | Add :param to path |
res.json after res.send | Response already sent | Check flow |
Real-World Examples
1. JSON API
app.use(express.json());
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }]);
});
app.post('/api/users', (req, res) => {
const user = req.body;
res.status(201).json(user);
});
2. Static Files
app.use(express.static('public'));
3. CORS
app.use((req, res, next) => {
res.set('Access-Control-Allow-Origin', '*');
next();
});
4. Logging
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});
5. Auth Middleware
function auth(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).send('Unauthorized');
}
next();
}
app.get('/admin', auth, (req, res) => {
res.send('Admin page');
});
6. Route Parameters
app.get('/users/:id', (req, res) => {
res.send(`User ${req.params.id}`);
});
7. Query Parameters
app.get('/search', (req, res) => {
res.send(`Query: ${req.query.q}`);
});
8. Redirect
app.get('/old', (req, res) => {
res.redirect('/new');
});
9. Error Handling
app.get('/error', (req, res) => {
throw new Error('Something went wrong');
});
app.use((err, req, res, next) => {
res.status(500).json({ error: err.message });
});
10. Modular Routes
const router = express.Router();
router.get('/', (req, res) => res.send('List'));
router.post('/', (req, res) => res.send('Create'));
app.use('/users', router);
Visual
Express Application Structure
┌─────────────────────────────────────────────────────────────┐
│ const app = express() │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Middleware (app.use) │ │
│ │ - logger │ │
│ │ - express.json() │ │
│ │ - cors │ │
│ └─────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Routes │ │
│ │ - GET /users │ │
│ │ - POST /users │ │
│ │ - GET /users/:id │ │
│ └─────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Error Handler (app.use with 4 args) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Request → middleware → route → response │
│ │
└─────────────────────────────────────────────────────────────┘
Middleware Flow
┌─────────────────────────────────────────────────────────────┐
│ REQUEST │
│ │ │
│ ▼ │
│ Middleware 1 ────next()────▶ │
│ │ │
│ ▼ │
│ Middleware 2 ────next()────▶ │
│ │ │
│ ▼ │
│ Route Handler ────res.send()──▶ RESPONSE │
│ │
│ If middleware does not call next(), the chain stops. │
│ If middleware calls res.send(), the response is sent. │
│ │
└─────────────────────────────────────────────────────────────┘
Request and Response Objects
┌─────────────────────────────────────────────────────────────┐
│ req (IncomingMessage + Express) │
│ ├── req.params (path parameters) │
│ ├── req.query (query string) │
│ ├── req.body (parsed body) │
│ ├── req.method (GET, POST, ...) │
│ ├── req.headers (request headers) │
│ └── req.pipe() (stream methods) │
│ │
│ res (ServerResponse + Express) │
│ ├── res.send() (send response) │
│ ├── res.json() (send JSON) │
│ ├── res.status() (set status) │
│ ├── res.redirect() (redirect) │
│ ├── res.set() (set header) │
│ └── res.write() (stream methods) │
│ │
└─────────────────────────────────────────────────────────────┘
Routing Methods
┌─────────────────────────────────────────────────────────────┐
│ app.get('/path', handler) → GET only │
│ app.post('/path', handler) → POST only │
│ app.put('/path', handler) → PUT only │
│ app.delete('/path', handler) → DELETE only │
│ app.all('/path', handler) → ALL methods │
│ │
│ app.get('/users/:id', handler) → param in req.params │
│ app.get('/search', handler) → query in req.query │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Framework | Express |
| Install | npm install express |
| Create app | const app = express() |
| Start server | app.listen(port) |
| Route | app.get(path, handler) |
| Path parameters | req.params |
| Query parameters | req.query |
| Middleware | app.use(fn) |
| Error handler | app.use((err, req, res, next) => {}) |
| Response methods | res.send, res.json, res.status |
| Sub-applications | app.use('/path', subApp) |
| Node.js version | 18+ |
Key takeaways:
- Express is a minimal framework over
node:http. It adds routing, middleware, and convenience methods, but the underlying request and response objects are the same ones the raw module provides. - Routes are defined with HTTP method names.
app.get(),app.post(),app.put(), andapp.delete()map methods and paths to handlers.app.all()matches every method for a path. - Path parameters are prefixed with
:. They are available inreq.params. Query string parameters are available inreq.query. - Middleware functions run before handlers. They receive
(req, res, next)and either callnext()to pass control or end the response. They can be applied globally, to a path prefix, or to a specific route. - Error handlers take four arguments. They are defined after all other middleware and routes. Express calls them when an error is passed to
next(). - The
reqandresobjects are the raw Node.js objects with Express methods attached. The stream methods (req.pipe(),res.write()) are still available alongside the Express methods (res.send(),res.json()). - Express does not force a project structure. It supports any template engine, any database library, and any folder layout. The framework provides the HTTP layer; the application provides everything else.
Remember: Express is the framework that most Node.js web applications are built on. It is not the only one—Fastify, Koa, and Hono are alternatives—but it is the most widely used and the one with the largest ecosystem. It provides the routing and middleware that the raw http module does not, while staying close enough to the primitives that the underlying behavior remains visible. The handler receives the same request and response objects that the raw module provides. The middleware chain is the mechanism that applies cross-cutting concerns. The route methods are the mechanism that maps URLs to handlers. Understand those three things—the request, the response, and the middleware chain—and the rest of Express is details.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!