A Practical Guide to Express Routing

Summary: in this tutorial, you’ll learn about Express routing, a process of defining endpoints (URIs) and responding when the client requests those endpoints.

Introduction to Express Routing #

An endpoint is a specific URL (Uniform Resource Locator) or path and an HTTP method (GET, POST, PUT, …)

Typically, an endpoint represents a specific resource or function which a client can perform such as fetching data, modifying data, and deleting records.

For example, the following endpoint retrieves a list of todos:

GET /todos

In this endpoint, The path is /todos and the HTTP method is GET.

The following endpoint creates a new todo item:

POST /todos

The path is also /todos but the HTTP is POST.

In Express, routing is about setting up endpoints and defining how the app should respond when a client requests to those endpoints.

A route defines how your app handles requests to an endpoint. To define a route, you use a method of the Express object such as get, post, put, patch, and delete and a

Here’s the syntax for defining a route:

app.method(path, handler);

In this syntax:

  • app is an instance of the Express application.
  • method is an HTTP request method in lowercase e.g., get, post, put, patch, and delete.
  • path is the path on the server.
  • handler is a function executed when the route is matched. The route handler has access to the req and res objects, which correspond to HTTP request and response objects.

Basic route examples #

The following defines a route for the GET requests to the root URL (‘/’):

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

Similarly, the following defines a route for the GET request to '/about':

app.get('/about', (req, res) => {
   res.send('About Page');
});

The following example defines a route for POST requests to '/login':

app.post('/login', (req, res) => {
   res.send('Authenticated');
});

Express routing examples #

Let’s create an Express app with a couple of routes.

Creating an Express app #

Step 1. Open your terminal or command prompt and create a new directory such as express-routing:

mkdir express-routing
cd express-routing

Step 2. Run the following npm command to initialize the project:

npm init -y

This will create a package.json file that includes configurations, scripts, and dependencies.

Step 3. Install Express by running the following npm command from the terminal:

npm install express

Step 4. Add the .env file to the project and set the PORT to 3000:

PORT=3000

Step 5. Add the following key/value pair to the package.json file to instruct Node.js to use ES modules:

"type": "module"

Also, change the scripts section to the following:

"scripts": {
    "start": "node --watch --env-file=.env index.js"
  },

This allows you to run the npm start command to execute the index.js file with the --watch flag and load the .env file.

Step 6. Create an index.js file with the following code:

import express from 'express';

const app = express();
const PORT = process.env.PORT || 3000;


app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

Step 7. Start the server by running the following command from the terminal:

npm start

Step 8. Create an api.http file for making requests to routes using REST Client extension in VS Code.

Adding routes #

Step 1. Create a new module todos.js module that exports an array of todo items and a function that returns the next todo id:

export const todos = [
  { id: 1, title: 'Learn Node.js', completed: true },
  { id: 2, title: 'Master Express', completed: false },
  { id: 3, title: 'Build an API Server', completed: false },
];

export const nextTodoId = () => {
  // get the next id for the todo
  let maxId = 1;
  todos.forEach((todo) => {
    if (todo.id > maxId) {
      maxId = todo.id;
    }
  });

  return maxId + 1;
};

Step 2. Import the todos array and nextTodoId function from the todo.js module into the index.js module:

import { todos, nextTodoId } from './todos.js';

Step 3. Define a route for a GET request to the '/api/todos/' endpoint:

import express from 'express';
import { todos, nextTodoId} from './todos.js';

const app = express();

const PORT = process.env.PORT || 3000;

app.get('/api/todos/', (req, res) => {
  res.send(todos);
});

app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

Step 4. Modify the api.http by adding the HTTP request to the /api/todos endpoint:

# Get all todos items
GET http://localhost:3000/api/todos/

When a GET request is made to the /api/todos/ endpoint, the route handler is executed, which responds with the todos array as JSON data:

[
  {
    "id": 1,
    "title": "Learn Node.js",
    "completed": true
  },
  {
    "id": 2,
    "title": "Master Express",
    "completed": false
  },
  {
    "id": 3,
    "title": "Build an API Server",
    "completed": false
  }
]

Adding a query string to a route #

To retrieve a list of completed or incompleted todo items, you can append a query string to the route /api/todos like this:

http://localhost:3000/api/todos/?completed=true

or

http://localhost:3000/api/todos/?completed=false

Everything after the question mark (?) in the URL is called a query string. In our example, the query string is the following:

completed=false

A query string may include one or more parameters, where each parameter is represented by a unique key/value pair separated by an equals sign (=).

To access the query string from the URL in Express, you use the req.query object like this:

req.query

For example:

// ... 

app.get('/api/todos/', (req, res) => {
  console.log(req.query);
});

// ...

If you make a GET request to the endpoint http://localhost:3000/api/todos/?completed=false, you’ll see the following query object in the console:

{ completed: 'false' }

To access the completed parameter in the query string, you can use the following:

app.get('/api/todos/', (req, res) => {
  console.log(req.query.completed);
});

The following modifies the /api/todos/ endpoint to return the incompleted/completed todo items if the completed query string is available:

app.get('/api/todos/', (req, res) => {
  if (req?.query?.completed) {
    const isCompleted = req.query.completed === 'true';
    const filteredTodos = todos.filter(
      (todo) => todo.completed === isCompleted
    );
    res.send(filteredTodos);
  }

  res.send(todos);
});

Here’s the updated version of the api.http file:

# Get all todos items
GET http://localhost:3000/api/todos/

### 

# Get incompleted todo items:
GET http://localhost:3000/api/todos/?completed=false

### 

# Get completed todo items:
GET http://localhost:3000/api/todos/?completed=true

Handling route parameters #

A route segment is a named URL segment used to capture the value specified at its position in the URL. For example:

http://localhost:3000/api/todos/1

In this URL, 1 is a route parameter representing the todo id.

In Express, you can set up the route with an id like this:

/api/todo/:id

To access route parameters, you use the req.params object:

req.params

In the route /api/todo/:id, the id is a key to the req.params object. So you can access it using the following:

req.params.id

Note that the values of route parameters are always strings. Therefore, you need to convert them into the values of desired types.

The following defines a route that returns a todo item by an id:

app.get('/api/todos/:id', (req, res) => {
  const { id } = req.params;

  const todoId = parseInt(id);
  if (isNaN(todoId)) {
    res.status(400).send(`Invalid id ${id}`);
  }

  const todo = todos.find((todo) => todo.id === todoId);
  if (todo) res.status(200).send(todo);

  res.status(404).send(`Todo with id ${id} not found`);
});

How it works.

First, get the id from the req.params object by destructing the object:

const { id } = req.params;

Second, convert the id value to an integer. If the id is not a number, respond with HTTP status code 400 with an error message:

const todoId = parseInt(id);
if (isNaN(todoId)) {
   res.status(400).send(`Invalid id ${id}`);
}

Third, find the todo with the id and return it as JSON to the client:

const todo = todos.find((todo) => todo.id === id);
if (todo) res.send(todo);

Finally, return the HTTP status code 404 if the todo with the id is not found:

res.status(404).send(`Todo with id ${id} not found`);

Putting it all together #

The following shows the completed index.js module:

import express from 'express';
import { todos, nextTodoId } from './todos.js';

const app = express();

const PORT = process.env.PORT || 3000;

app.get('/api/todos/', (req, res) => {
  if (req?.query?.completed) {
    const isCompleted = req.query.completed === 'true';
    const filteredTodos = todos.filter(
      (todo) => todo.completed === isCompleted
    );
    res.send(filteredTodos);
  }

  res.send(todos);
});

app.get('/api/todos/:id', (req, res) => {
  const { id } = req.params;

  const todoId = parseInt(id);
  if (isNaN(todoId)) {
    res.status(400).send(`Invalid id ${id}`);
  }

  const todo = todos.find((todo) => todo.id === todoId);
  if (todo) res.status(200).send(todo);

  res.status(404).send(`Todo with id ${id} not found`);
});

app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

Download the Express Routing example source code

Summary #

  • An endpoint refers to a specific URL (or path) and an HTTP method that a client can use to access a resource or perform an operation function.
  • A route defines how the app responds to a particular endpoint.
  • Use methods of Express app object to define routes.

Was this helpful?