Clara Ekekenta Software Engineer and perpetual learner with a passion for OS and expertise in Python, JavaScript, Go, Rust, and Web 3.0.

Express.js 5 migration guide

8 min read 2297

Express Logo

Express.js is among the most popular Node.js frameworks, and new versions are frequently published to upgrade or add new functionality. Some past releases have minimized the framework’s memory footprint, improved page load speeds, added new APIs and framework integrations, ensured code maintainability, and provided a better shorthand technique for calling and using functions.

The newly released Express.js v5 includes a slew of new features and enhancements aimed at improving the framework’s efficiency. However, this update may cause problems or generate deprecation warnings, making the migration a little unnerving.

In this tutorial, we’ll demonstrate how to upgrade from Express v4 to Express v5. We’ll also take a look at the upgrades and new features available in Express 5, and we’ll see how to build a web app in Express 5.

Let’s get going!

Jump ahead:

Express.js overview

Express is a Node.js backend web application framework released under the MIT License as free and open source software. This popular Node web framework serves as the foundation for many other Node web frameworks, with a mechanism to:

  • Create handlers for requests with various HTTP verbs and URL paths (routes)
  • Integrate with “view” rendering engines to provide replies by populating templates with data
  • Set up common web application settings such as the connection port and the location of templates for producing the answer
  • Accommodate the addition of extra request processing middleware at each stage of the request handling pipeline

TJ Holowaychuk, the original author of Express, referred to it as a Sinatra-inspired server, a reference to its simplicity and to its numerous functionalities that are available as plugins.

Express, along with the MongoDB database software as well as a JavaScript frontend framework or library, is the backend component of makes up popular tech stacks such as MEAN, MERN, and MEVN.

Why migrate to Express 5?

Express 5 is still in beta, but as I mentioned earlier, it offers several updates over Express 4. These improvements are intended to increase the efficiency of the framework. The most significant changes relate to path route matching syntax. Let’s take a closer look.

Path route matching syntax

In Express 5, several changes have been made to the way a path string is matched to an incoming request:

  • New parameter modifiers: ?*, and +
  • Named matching groups: no longer available by position in req.params
  • Regular expressions: may only be used in a matching group; /(d+) replaces /\\d+
  • Matching group expressions: can only use regex syntax; (.*) replaces (*)
  • Special * path segment behavior: this has been removed; /id/*/user will match a literal * as the middle segment

The string passed as the first input to the app.all(), app.use(), app.METHOD(), router.all(), router.METHOD(), or router.use() APIs is referred to as the path route matching syntax.

Rejected promises and middleware handlers

In Express 4, users rely on packages like express-async-handler or write their helper functions to catch rejections of handlers that return promises.

However, in Express 5, any rejected promise or error thrown from middleware or handlers is forwarded as an error to the error handling middleware. This helps prevent the app from crashing.

This makes it easier to use async functions as middleware and handlers. When an async function throws an error or a rejected promise is awaited inside an async function, the error is sent to the error handler by calling the next (err) function.

app.use(async (req, res, next) => {
  throw new Error("Something went wrong");

The above is equivalent to returning a Promise explicitly from a middleware or route handler in Express 4:

app.use((req, res, next) => {
  return new Promise((resolve, reject) => {
    reject(new Error("Something went wrong"));

Return of the app.router object

Express 5 has brought back the app.router object, which was removed in Express 4. Unlike Express 3, in which an app was required to explicitly load the object, this object in Express 5 is essentially a reference to the underlying Express router.

Port maintained in object

Express 5 maintains the port number in the object. Previously, if a port number was provided, the method in Express 4 improperly removed the number.

More great articles from LogRocket:

req.query object changed to getter

The req.query property in Express 5 has been changed from a writable property to a getter. Additionally, the default query parser has been renamed “simple” from “extended”.

Express v4 vs. Express v5

Except for the updates mentioned previously, Express 5 is very similar to Express 4. The API hasn’t changed nearly as much as it did from v3 to v4. However, there are certain breaking changes, even though the underlying API has remained the same. In other words, updating an old Express 4 program to use Express 5 may cause it to fail.

Let’s take a closer look at the breaking changes:

  • app.del(): this feature is no longer supported in Express 5, and an error will be thrown if you use this function
    • Solution: use the app.delete() function in place of app.del(); delete is a reserved keyword in JavaScript, so del was initially used instead of delete. In ECMAScript 6, delete and other reserved keywords can now be properly used as property names:
      app.del(‘/user’, (req, res) => res.send(‘user deleted’));
      // Will give deprecation warning in Express v4
      // Will throw TypeError in Express v5
  • app.param(name, fn): this function has been depreciated since v4.11.0, and is not supported by Express 5; its functionality was changed using the app.param(fn) signature
  • req.acceptsCharset(), req.acceptsEncoding(), req.acceptsLanguage() have been pluralized is Express 5
    • Solution: use the following instead: req.acceptsCharsets(), req.acceptsEncodings(), and req.acceptsLanguages()
  • req.param(name): it’s not possible to retrieve form data with this function in Express 5
    • Solution: to retrieve form data, look for the submitted parameter name in the req.params, req.body, or req.query object as shown in the following example:
      app.get(‘/user/:id’, (req, res) => {
      // Will give deprecation warning in Express v4
      // Will throw TypeError in express v5 "req.param is not a function"
      // Do this instead in v5
      app.get(‘/user/:id’, (req, res) => {
  • res.json: this signature is not supported by Express 5. (obj, status).
    • Solution: use the res.status(status).json(obj syntax to change the status and then chain it to the res.json() method, like so:
      // Version 4
      app.get(‘/user/:id’, (req, res) => {
        res.json(200, {user: User.find(});
      // Version 5
      app.get(‘/user/:id’, (req, res) => {
          user: User.find(
  • res.send(obj, status): this signature is not supported in Express 5
    • Solution: use res.status(status).send() to set the status and then chain it to the res.send() method, like so:
      // Version 4
      app.get(‘/user/:id’, (req, res) => {
        res.send(200, {user: User.find(});
      // Version 5
      app.get(‘/user/:id’, (req, res) => {
          user: User.find(
  • res.send(status): this signature, where status is an integer, is not supported in Express 5
    • Solution: use the res.sendStatus(statusCode) function, which delivers the text version of the HTTP response header status code, such as “Not Found” or “Internal Server Error”; if you need to send a number using the res.send() function, use quote marks to convert it to a string – otherwise, Express 5 may think you’re trying to use an unsupported old signature
      // Old method
      // Version 5

Migrating an Express 4 application to Express 5

Migrating your Express 4 application to Express 5 is not as big of a lift as you may think.

To get started, move into the root directory of your project and install Express 5 using the following command:

npm install "[email protected]>=5.0.0-beta.1" --save

This command will update your project dependencies and also update the package.json file.

Next, rebuild the project or rerun your tests to determine if anything failed. Use the updates covered in this tutorial to fix any issues that may have occurred.

If you’d like to update any other npm packages in the project, run the npm outdated command. This will enable you to see other packages in your project that need to be updated. Then, update them using the npm update command.

Express v5 web app demo

To demonstrate what we’ve learned so far about Express 5, let’s create a demo todo application using the Express 5 syntax.

To get started, create a new folder for the application, like so:

mkdir express-5-demo && cd express-5-demo

Then, initialize a new Node.js project:

npm init -y

The above command will generate a package.json file. Open the file and add a start script:

  "start": "node index.js"

Install dependencies

Now, install Express 5 and other required packages for this application with the following command:

npm i [email protected] body-parser

Wait for the installation to finish before continuing. Once you’re certain that the installation is complete, create an index.js file in the project’s root directory, and set up a basic Express server using the following code snippet:

const express = require("express");
const app = express();  
app.listen(3000, ()=>console.log("Express server running on port 3000"));

Create routes

With the Express server configured, let’s create some basic routes for the application. Update the index.js file with the following code snippets:

  const todos = [{
        id: 1,
        title: "Learn Node.js",
        completed: true
        id: 2,
        title: "Learn React.js",
        completed: false
        id: 3,
        title: "Learn Angular.js",
        completed: false
    app.get("/todos", (req, res) => {
    });"/todos", (req, res) => {
        const todo = req.body;
    app.get("/todos/:id", (req, res) => {
        const id = parseInt(;
        const todo = todos.find(todo => === id);
    app.put("/todos/:id", (req, res) => {
        const id = parseInt(;
        const todo = todos.find(todo => === id);
        todo.completed = !todo.completed;
    app.delete("/todos/:id", (req, res) => {
        const id = parseInt(;
        const todo = todos.find(todo => === id);
        todos.splice(todos.indexOf(todo), 1);

In the above code, we created a todos array with some dummy data. Then, we defined four API routes to get, add, update, and delete a todo from the application.

Create middleware

To allow users to create a new todo, we need to define a middleware for the body-parser package we just installed. Add the following code before the todos array:


Serve static files

We’ve completed the setup for our routes, so let’s create another router and middleware to serve up an HTML page to display the todos to the users.

First, create a new folder and name it public. Then, create a new file called index.html. Open the index.html file and add the following code snippet:

    <html lang="en">
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <ul id="data"></ul>
            list-style: none;
            padding: 0;
        #data li{
            display: inline-block;
            margin: 10px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
    <script src="app.js"></script>

In the above code, we created an HTML document to display the todos. The todos will be displayed in the <ul id="data"></ul> section of the page, which we’ll handle with JavaScript. We also defined some styling and referenced the JavaScript file which we’ll create shortly to get all the todos.

Now create an app.js file in the public folder and add the following code:

const Tododata = document.getElementById('data');
    fetch('http://localhost:3001/todos', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        .then(res => res.json())
        .then(data => {
              data.forEach(element => {
                  const li = document.createElement('li');
                  li.innerHTML = element.title;
        .catch(err => console.log(err));

Here we send a request to the backend, using the JavaScript fetch API, to check if a todo exists in the todos array. Then we create a list element to display the details of the todos.
For the page to render, we need to add one more configuration to the root index.js file. Add the code below after const app = express();

app.use(express.static(__dirname + "/public"));

Now we’ll be able to render all the static files in the public directory.

Test the application

To test the application, run the following command to start the Express server:

npm start

Then, navigate in your browser to localhost:3000, and you should see the following output:

Todos List to Learn Node, React, and Angular


In this article, we explored the new features and improvements available in Express.js v5 and reviewed how to migrate an existing Express 4 app to Express 5. To demonstrate how to take advantage of the new functionalities available in version 5, we created a step-by-step demo showing how to create a web app in Express 5.

What are your thoughts on Express 4 vs. Express 5? Do you plan to migrate to Express 5 in your next project? For additional information about Express 5, check out the official documentation.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Clara Ekekenta Software Engineer and perpetual learner with a passion for OS and expertise in Python, JavaScript, Go, Rust, and Web 3.0.

Leave a Reply