| |

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

CommandPurpose
npm init -yCreate package.json
npm install expressInstall Express
node app.jsRun the app

Routing Methods

MethodHTTP Verb
app.get()GET
app.post()POST
app.put()PUT
app.delete()DELETE
app.all()All methods

Request Properties

PropertyValue
req.paramsPath parameters
req.queryQuery string parameters
req.bodyParsed body (with middleware)
req.methodHTTP method
req.urlFull URL
req.pathPath without query
req.headersRequest headers

Response Methods

MethodPurpose
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

TypeSignaturePurpose
Regular(req, res, next)Runs before handlers
Error(err, req, res, next)Handles errors
app.use()GlobalAll routes
app.use('/path')Path-scopedPath prefix
app.get('/path', mw)Route-scopedSpecific 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

PitfallWhy It HappensFix
Request hangsMiddleware did not call next()Call next()
Body is undefinedNo body parserapp.use(express.json())
404 for defined routeWrong method or pathCheck method and path
Error handler not calledDefined before routesMove to the end
req.params emptyParameter not in pathAdd :param to path
res.json after res.sendResponse already sentCheck 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

ItemValue
FrameworkExpress
Installnpm install express
Create appconst app = express()
Start serverapp.listen(port)
Routeapp.get(path, handler)
Path parametersreq.params
Query parametersreq.query
Middlewareapp.use(fn)
Error handlerapp.use((err, req, res, next) => {})
Response methodsres.send, res.json, res.status
Sub-applicationsapp.use('/path', subApp)
Node.js version18+

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(), and app.delete() map methods and paths to handlers. app.all() matches every method for a path.
  • Path parameters are prefixed with :. They are available in req.params. Query string parameters are available in req.query.
  • Middleware functions run before handlers. They receive (req, res, next) and either call next() 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 req and res objects 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!