Premium lesson

Routing HTTP Requests in Express.js

Express.js·66 min read·Feb 12, 2025

In web development, routing refers to the mechanism by which a web application determines how to process and respond to the requests sent by clients on a specified endpoint, which as a reminder, is a mapping between a specific HTTP method and a URL.

Declaring routes

In Express, each endpoint (or route) is individually declared using this syntax:

server.METHOD(PATH, HANDLER)

Where:

  • server is an Express instance created using the top-level express() function.

  • METHOD is a function representing an HTTP verb.

  • PATH is a string representing a URN on the server.

  • HANDLER is a request listener function, also called a controller, that is executed when the route is matched.

Note: In Express, incoming requests are compared to each endpoint in a declarative manner — from top to bottom — until the request matches an endpoint's HTTP verb and path, or is implicitly forwarded to the built-in error handling component if none match.

Route methods

Express supports methods that correspond to all HTTP verbs, including but not limited to:

  • get() to handle HTTP GET requests

  • post() to handle HTTP POST requests

  • put() to handle HTTP PUT requests

  • delete() to handle HTTP DELETE requests

Additionally, Express provides a special routing method named all() that matches all HTTP verbs for a specified path, which means that its handler will be executed whether using GET, POST, PUT, or any other HTTP verb supported by Node's http core module.

Route paths

Route paths can be strings, string patterns, or regular expressions, where:

  • The characters ?, +, *, and () are subsets of their regular expression counterparts.

  • The characters - and . are interpreted literally by string-based paths.

Example

Let's consider the following paths and their interpretations:

'/foo'         // Will match '/foo'
'/foo-bar'     // Will match '/foo-bar'
'/foo.bar'     // Will match '/foo.bar'
'/bar?'        // Will match '/ba' or '/bar'
'/baz+'        // Will match one of '/baz', '/bazz', '/bazzz', ...
'/foo*'        // Will match one of '/foo', '/fooABCD', '/fooAB12', ...
'/foo(bar)?'   // Will match '/foo' or '/foobar'

Route handlers

A route handler, also called a request listener or controller, is a callback function that is executed whenever an incoming request matches a specified endpoint:

server.METHOD(PATH, (req, res) => {
  //
});

Where:

  • req is an object representing the incoming HTTP request sent by the client.

  • res is an object representing the outgoing HTTP response sent by the server.

The request object

The request object is essentially used to parse an incoming HTTP request and has access to various information, like:

  • The req.method string contains the HTTP method of the request.

  • The req.path string contains the URN the request was sent to.

  • The req.headers object contains the set of headers of the request.

  • The req.body object contains the message body of the request.

The response object

The response object is essentially used to send a response back to the client and has access to various methods, like:

  • The res.send() method for sending the message body of the response.

  • The res.status() method for setting the status code of the response.

  • The res.cookie() method for setting the cookies of the response.

Example

Let's consider this server, that implements an HTTP GET route and a fallback route:

// File: server.js

const express = require('express');

const server = express();

server.get('/hello', (req, res) => {
  res.send('Hello, World!');
});

server.all('*', (req, res) => {
  res.status(404).send(`Error: ${req.method} ${req.path}`);
});

server.listen(3000);

When executed, it will:

  1. Catch every incoming HTTP GET request reaching the server on the '/hello' path using the get() method, and respond with an HTTP 200 OK containing the string 'Hello, World!' using the send() method of the response object.

  2. Catch every other incoming HTTP request, regardless of its HTTP verb or path using the all() method combined with the wildcard path '*', and respond with an HTTP 404 Not Found containing the request's verb and path using a combination of the status() and send() methods of the response object.

You can test this server using these curl commands in a different terminal window:

$ curl 127.0.0.1:3000/hello
Hello, World!
$ curl 127.0.0.1:3000/foobar
Error: GET /foobar

Note: When sending a request to the root path of a server, the forward slash character / is optional, which means that 127.0.0.1:3000/ and 127.0.0.1:3000 are equivalent addresses.

Advanced route paths

In Express, route parameters and query strings are two mechanisms that allow clients to dynamically pass additional values to the server through the URL.

Route parameters

Route parameters are placeholders in a URN pattern that allow the server to capture specific parts of the URN.

They are preceded by a colon character : and made up of "word characters" [A-Za-z0-9_].

'/:name'

When a request reaches the server on the specified endpoint, they are automatically added to the params property of the controller's request object.

server.METHOD('/:name', (req, res) => {
  // req.params.name
});

Note: When added to the params object, route parameter keys are automatically stripped from their preceding colon character : (e.g., :city -> req.params.city), and their value is assigned to the corresponding key.

Example

Let's consider this JSON file named shows.json that contains information about TV shows:

{
  "chernobyl": [
    [
      {
        "title": "1:23:45",
        "duration": 60,
        "release_date": "2019-05-06",
        "summary": "The explosion at the Chernobyl Nuclear Power Plant triggers a devastating crisis, and Soviet officials scramble to respond amidst a fog of misinformation and denial."
      },
      {
        "title": "Please Remain Calm",
        "duration": 60,
        "release_date": "2019-05-13",
        "summary": "As the truth of the disaster emerges, a physicist and a government official begin efforts to limit the damage, facing the bureaucracy's unwillingness to act."
      },
      {
        "title": "Open Wide, O Earth",
        "duration": 60,
        "release_date": "2019-05-20",
        "summary": "Heroic individuals, including miners and first responders, risk their lives to stabilize the reactor, while the extent of the environmental damage becomes clear."
      },
      {
        "title": "The Happiness of All Mankind",
        "duration": 60,
        "release_date": "2019-05-27",
        "summary": "The human cost of the disaster becomes undeniable as liquidators are sent into radioactive zones. The government struggles to maintain control of the narrative."
      },
      {
        "title": "Vichnaya Pamyat",
        "duration": 60,
        "release_date": "2019-06-03",
        "summary": "In a dramatic conclusion, the events leading to the explosion are reconstructed, and a trial highlights the systemic failures that enabled the disaster."
      }
    ]
  ]
}

Let's consider this server:

const express = require('express');
const shows = require('./shows.json');

const server = express();

server.get('/tv/:show/season-:season/episode-:episode', (req, res) => {
  const { show, season, episode } = req.params;
  const result = shows?.[show]?.[parseInt(season - 1)]?.[parseInt(episode - 1)] || null;
  const statusCode = result ? 200 : 404;
  const messageBody = result || {};

  res.status(statusCode).send(messageBody);
});

server.all('*', (req, res) => {
  res.status(404).send(`Error: ${req.method} ${req.path}`);
});

server.listen(3000);

You can test it using the following curl commands:

$ curl 127.0.0.1:3000/tv/chernobyl/season-1/episode-4
{"title":"The Happiness of All Mankind","duration":60,"release_date":"2019-05-27","summary":"The human cost of the disaster becomes undeniable as liquidators are sent into radioactive zones. The government struggles to maintain control of the narrative."}
$ curl 127.0.0.1:3000/tv/chernobyl/season-2/episode-1
{}
$ curl 127.0.0.1:3000/tv/chernobyl/season-1
Error: GET /tv/chernobyl/season-1

Query string parameters

Query string parameters are strings part of an URI that assign values to specified parameters.

They are separated from the URL by a question mark ? and contain values in the form of a list of key-value pairs.

'/path?key1=value2&key2=value2'

Where:

  • The key and the value are separated by an equal sign =.

  • The pairs are separated by an ampersand &.

When a request reaches the server on the specified endpoint, they are automatically added to the query property of the controller's request object.

server.METHOD('/path', (req, res) => {
  // req.query.key1
  // req.query.key2
});

Note: Unlike route parameters, query string parameters are optional and are not considered as integral part of the endpoint's path. They are dynamically added to the query object only if present.

Example

In this example, the server implements a single HTTP GET route:

const express = require('express');

const server = express();

server.get('/weather', (req, res) => {
  const { city, country } = req.query;

  if (!city || !country) {
    res.status(400).send(`Unknown location`);
  } else {
    res.send(`The weather in ${city}, ${country} is currently sunny`);
  }
});

server.listen(3000);

When executed, it will:

  1. Check whether incoming requests sent to the HTTP GET /weather endpoint contain a city and a country query parameters.

  2. If false, respond with an HTTP 400 Bad Request containing the string 'Unknown location' indicating that the server cannot process the request due to something that is perceived as a client error.

  3. If true, respond with an HTTP 200 OK containing a string with the value of both parameters.

You can test it using the following curl commands:

$ curl 127.0.0.1:3000/weather
Unknown location
$ curl 127.0.0.1:3000/weather?city=Paris
Unknown location
$ curl '127.0.0.1:3000/weather?city=Paris&country=France'
The weather in Paris, France is currently sunny

Note: In the last case, the request's URL must be enclosed in single quotes '' to prevent the ampersand & from being interpreted by the shell and the curl command from being executed as a background job.

Routers

In Express, a router is an isolated object that can be thought of as a "mini application" capable of performing routing functions among other things.

It allows to group together several routes belonging to the same business domain, which is the specific area a software solution addresses, such as handling payments or customers, under a common URL called a mount path.

Creating a new router

To create a new router, you can use the Router() method exported by the express module:

const express = require('express');

const router = express.Router();

Attaching routes to a router instance

To attach routes to a router instance, you can use the same syntax as with regular routes:

router.METHOD(PATH, HANDLER);

Where:

  • router is a router instance created using the top-level Router() function.

  • METHOD is a function representing an HTTP verb like get(), post(), etc.

  • PATH is a string of characters representing URN like /signup.

  • HANDLER is a request listener function, also called a controller, that is executed when the route is matched.

Mounting a router to the server instance

To mount a router instance to the application, you can use the use() method of the application instance:

server.use(MOUNTPATH, ROUTER);

Where:

  • MOUNTPATH is the base URN the router will be attached to.

  • ROUTER is a router instance.

Example

Let's consider this JSON file named recipes.json that contains information about cooking recipes:

{
  "breakfast": {
    "pancakes": {
      "ingredients": [
        { "item": "All-purpose flour", "quantity": "1 cup" },
        { "item": "Sugar", "quantity": "2 tablespoons" },
        { "item": "Baking powder", "quantity": "2 teaspoons" },
        { "item": "Milk", "quantity": "3/4 cup" },
        { "item": "Egg", "quantity": "1 large" },
        { "item": "Butter (melted)", "quantity": "2 tablespoons" }
      ],
      "instructions": [
        "Combine flour, sugar, and baking powder in a bowl.",
        "Whisk milk, egg, and melted butter in another.",
        "Mix until smooth.",
        "Cook on a griddle over medium heat until bubbles form and edges are set."
      ]
    }
  },
  "dessert": {
    "cookies": {
      "ingredients": [
        { "item": "All-purpose flour", "quantity": "1 1/4 cups" },
        { "item": "Butter (softened)", "quantity": "1/2 cup" },
        { "item": "Granulated sugar", "quantity": "1/2 cup" },
        { "item": "Brown sugar", "quantity": "1/4 cup" },
        { "item": "Egg", "quantity": "1 large" },
        { "item": "Baking soda", "quantity": "1/2 teaspoon" },
        { "item": "Chocolate chips", "quantity": "1 cup" }
      ],
      "instructions": [
        "Cream butter, granulated sugar, and brown sugar together.",
        "Beat in the egg.",
        "Stir in dry ingredients, followed by chocolate chips.",
        "Shape dough into balls and bake at 350°F for 10-12 minutes."
      ]
    }
  }
}

Let's consider this server, that implements a router mounted on the /recipes mountpath that regroups 3 routes.

const express = require('express');
const recipes = require('./recipes.json');

const server = express();
const router = express.Router();

router.get('/', (req, res) => {
  const messageBody = Object.keys(recipes).reduce((items, category) => {
    items[category] = Object.keys(recipes[category]);
    return items;
  }, {});

  res.send(messageBody);
});

router.get('/:category', (req, res) => {
  const { category } = req.params;
  const result = Object.keys(recipes[category] || {});

  const statusCode = result ? 200 : 404;
  const messageBody = result;

  res.status(statusCode).send(messageBody);
});

router.get('/:category/:recipe', (req, res) => {
  const { category, recipe } = req.params;
  const result = recipes[category][recipe] || {};

  const statusCode = result ? 200 : 404;
  const messageBody = result;

  res.status(statusCode).send(messageBody);
});

server.use('/recipes', router);

server.listen(3000);

When executed, it will:

  1. Catch incoming requests sent to the HTTP GET /recipes/ endpoint and respond with a JSON object containing the category and recipe names.

  2. Catch incoming requests sent to the HTTP GET /recipes/:category endpoint and respond with an array containing the recipe names of the specified category or an empty array.

  3. Catch incoming requests sent to the HTTP GET /recipes/:category/:recipe endpoint and respond with a JSON object containing the recipe or an empty object.

You can test it using the following curl commands:

$ curl 127.0.0.1:3000/recipes
{"breakfast":["pancakes"],"dessert":["cookies"]}
$ curl 127.0.0.1:3000/recipes/breakfast
["pancakes"]
$ curl 127.0.0.1:3000/recipes/breakfast/pancakes
{"ingredients":[{"item":"All-purpose flour","quantity":"1 cup"},{"item":"Sugar","quantity":"2 tablespoons"},{"item":"Baking powder","quantity":"2 teaspoons"},{"item":"Milk","quantity":"3/4 cup"},{"item":"Egg","quantity":"1 large"},{"item":"Butter (melted)","quantity":"2 tablespoons"}],"instructions":["Combine flour, sugar, and baking powder in a bowl.","Whisk milk, egg, and melted butter in another.","Mix until smooth.","Cook on a griddle over medium heat until bubbles form and edges are set."]}

🗒️ Summary

Here's a summary of what you've learned in this lesson:

  • The server.METHOD(PATH, HANDLER) syntax is used to declare routes.

  • Express supports the .get() method for HTTP GET requests, the .post() for HTTP POST requests, and so on.

  • Route paths are URNs in the form of strings, string patterns, or regular expressions.

  • Route handlers are callback functions executed when an incoming request matches a route's method and path.

  • Route parameters are placeholders that allow the server to capture specific parts of an URN as variables.

  • Query string parameters are key-value pairs that allow clients to pass additional parameters to an URN.

  • The request object req is used to parse incoming HTTP requests.

  • The response object res is used to send HTTP responses to the client.

  • Routers allow to group together multiple routes under a common mountpath.

icon light bulb key

Unlock the Build RESTful APIs in Node.js module

Learn how to build real database-backed and framework-powered RESTful APIs with MySQL, Sequelize, and Express.

You get immediate access to:

  • 37 focused lessons across MySQL, Sequelize, backend, and Express
  • 4 real-world projects with commented solutions
  • Ongoing updates to this bundle
  • Lifetime access to this bundle
Unlock this module