Skip to content

Update Express tutorial to v5 #40247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

hamishwillee
Copy link
Collaborator

@hamishwillee hamishwillee commented Jul 8, 2025

This updates the Express Tutorial to v5

I wrote some tests in mdn/express-locallibrary-tutorial#327, then updated Express to v5 along with all dependencies - everything passed. Then ran all the code mods (npx @expressjs/codemod upgrade) suggested in the migration guide - nothing was changed. So I don't think I was using anything impacted by a changed or removed API.

I have updated the docs now. Mostly this was removing code to do async error handling in routes using asyncHandler since Express now handles that automatically. Also updated the Express sections on handling errors so this is more clear.
The route path handling is also different, so I had to update that section - it wasn't anything I was using in the demo, but it was significant for readers.

I also updated the database connection to to be done in bin/www since that allows for testing.
The way it was done in the app.js means that tests can't work (because you can't have multiple connections, and if you do this in the app there is already a connection by the time you get to the test runner).

Last of all I went through the lot and checked all the docs and versions of libraries. I did not retest the deployment though (mostly because I did that fairly recently).

Actions:

Fixes #38922

@github-actions github-actions bot added Content:Learn Learning area docs size/m [PR only] 51-500 LoC changed labels Jul 8, 2025
@@ -463,27 +462,8 @@ In order to use these you have to first install the database driver using npm. F
npm install mongodb
```

The database itself can be installed locally or on a cloud server. In your Express code you require the driver, connect to the database, and then perform create, read, update, and delete (CRUD) operations. The example below (from the Express documentation) shows how you can find "mammal" records using MongoDB.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI This was example for a very old version of Mongoose. Removing info.

Copy link
Contributor

github-actions bot commented Jul 8, 2025

Preview URLs (23 pages)
External URLs (17)

URL: /en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs
Title: Express web framework (Node.js/JavaScript)


URL: /en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data/Home_page
Title: Home page


URL: /en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/forms/Update_Book_form
Title: Update Book form


URL: /en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction
Title: Express/Node introduction


URL: /en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes
Title: Express Tutorial Part 4: Routes and controllers


URL: /en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website
Title: Express Tutorial Part 2: Creating a skeleton website

(comment last updated: 2025-07-15 07:47:47)

@@ -674,7 +674,7 @@ npm install mongoose

## Connect to MongoDB

Open **/app.js** (in the root of your project) and copy the following text below where you declare the _Express application object_ (after the line `const app = express();`).
Open **bin/www** (from the root of your project) and copy the following text below where you set the port (after the line `app.set("port", port);`).
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI moved the DB connection code to the app entry point called by the server to start everything. Makes no difference to the app, but decoupling means that we can use a different db for tests. More work needs to be done in later docs, as we do things to update this URL, and they will currently suggest doing it in app.js

@github-actions github-actions bot added size/l [PR only] 501-1000 LoC changed and removed size/m [PR only] 51-500 LoC changed labels Jul 14, 2025
@hamishwillee hamishwillee marked this pull request as ready for review July 15, 2025 07:45
@hamishwillee hamishwillee requested a review from a team as a code owner July 15, 2025 07:45
@hamishwillee hamishwillee requested review from chrisdavidmills and removed request for a team July 15, 2025 07:45
@hamishwillee hamishwillee requested a review from bsmth July 15, 2025 07:46
Copy link
Contributor

@chrisdavidmills chrisdavidmills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hamishwillee sterling work, sir. I've read through all the changes and made a few suggestions, but nothing major.

A couple of questions:

  • Do you need me to run through it all and make sure it all works, or have you already done this with confidence? I'm assuming you have, but happy to help with this if not.
  • I've not checked in detail whether you've missed any instances of mass replaced terms such as 4x -> 5x, but I'm assuming you've grepped for these?

```

The approach is exactly the same as described for the [Genre detail page](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page).
The route controller function uses `Promise.all()` to query the specified `Author` and their associated `Book` instances in parallel.
If no matching author is found an Error object is sent to the Express error handling middleware.
If no matching author is found an `Error` object is sent to the Express error handling middleware.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If no matching author is found an `Error` object is sent to the Express error handling middleware.
If no matching author is found, an `Error` object is sent to the Express error handling middleware.

```

> [!NOTE]
> We don't need to require any additional modules in this step, as we already imported the dependencies when we implemented the home page controller.

The approach is exactly the same as described for the [Genre detail page](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page).
The route controller function uses `Promise.all()` to query the specified `Book` and its associated copies (`BookInstance`) in parallel.
If no matching book is found an Error object is returned with a "404: Not Found" error.
If no matching book is found an `Error` object is returned with a "404: Not Found" error.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If no matching book is found an `Error` object is returned with a "404: Not Found" error.
If no matching book is found, an `Error` object is returned with a "404: Not Found" error.

@@ -52,7 +52,7 @@ If the genre does not exist in the database (i.e., it may have been deleted) the
In this case we want to display a "not found" page, so we create an `Error` object and pass it to the `next` middleware function in the chain.

> [!NOTE]
> Errors passed to the `next` middleware function propagate through to our error handling code (this was set up when we [generated the app skeleton](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website#app.js) - for more information see [Handling Errors](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction#handling_errors)).
> Errors passed to the `next` middleware function propagate through to our error handling code (this was set up when we [generated the app skeleton](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website#app.js) for more information see [Handling Errors](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction#handling_errors) and [Handling errors and exceptions in the route functions](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes#handling_errors_and_exceptions_in_the_route_functions)).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Errors passed to the `next` middleware function propagate through to our error handling code (this was set up when we [generated the app skeleton](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website#app.js) — for more information see [Handling Errors](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction#handling_errors) and [Handling errors and exceptions in the route functions](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes#handling_errors_and_exceptions_in_the_route_functions)).
> Errors passed to the `next` middleware function propagate through to our error handling code (this was set up when we [generated the app skeleton](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/skeleton_website#app.js). For more information, see [Handling Errors](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction#handling_errors) and [Handling errors and exceptions in the route functions](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes#handling_errors_and_exceptions_in_the_route_functions)).

@@ -48,7 +48,7 @@ It `awaits` on the promise returned by `Promise.all()` to get the specified `Boo
When the operations complete the function checks whether any books were found, and if none were found sends an error "Book not found" to the error handling middleware.

> [!NOTE]
> Not finding any book results is **not an error** for a search — but it is for this application because we know there must be a matching book record! The code above compares for (`book===null`) in the callback, but it could equally well have daisy chained the method [orFail()](<https://mongoosejs.com/docs/api/query.html#Query.prototype.orFail()>) to the query.
> Not finding any book results is **not an error** for a search — but it is for this application because we know there must be a matching book record! The code above compares for (`book===null`) in the callback, but it could equally well have daisy chained the method [`orFail()`](<https://mongoosejs.com/docs/api/query.html#Query.prototype.orFail()>) to the query.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Not finding any book results is **not an error** for a searchbut it is for this application because we know there must be a matching book record! The code above compares for (`book===null`) in the callback, but it could equally well have daisy chained the method [`orFail()`](<https://mongoosejs.com/docs/api/query.html#Query.prototype.orFail()>) to the query.
> Not finding any book results is **not an error** for a search, but it is for this application because we know there must be a matching book record! The code above tests for (`book===null`) in the callback, but it could equally well have daisy-chained the method [`orFail()`](<https://mongoosejs.com/docs/api/query.html#Query.prototype.orFail()>) to the query.

"compares for" sounded a bit odd to me.

@@ -249,16 +245,19 @@ setTimeout(() => {
console.log("Second");
```

Using non-blocking asynchronous APIs is even more important on Node than in the browser because _Node_ is a single-threaded event-driven execution environment. "Single threaded" means that all requests to the server are run on the same thread (rather than being spawned off into separate processes). This model is extremely efficient in terms of speed and server resources, but it does mean that if any of your functions call synchronous methods that take a long time to complete, they will block not just the current request, but every other request being handled by your web application.
Using non-blocking asynchronous APIs is even more important on Node than in the browser because _Node_ applications are often written as a single-threaded event-driven execution environment. "Single threaded" means that all requests to the server are run on the same thread (rather than being spawned off into separate processes). This model is extremely efficient in terms of speed and server resources, but it does mean that if any of your functions call synchronous methods that take a long time to complete, they will block not just the current request, but every other request being handled by your web application.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Using non-blocking asynchronous APIs is even more important on Node than in the browser because _Node_ applications are often written as a single-threaded event-driven execution environment. "Single threaded" means that all requests to the server are run on the same thread (rather than being spawned off into separate processes). This model is extremely efficient in terms of speed and server resources, but it does mean that if any of your functions call synchronous methods that take a long time to complete, they will block not just the current request, but every other request being handled by your web application.
Using non-blocking asynchronous APIs is even more important on Node than in the browser because _Node_ applications are often written as a single-threaded event-driven execution environment. "Single threaded" means that all requests to the server are run on the same thread (rather than being spawned off into separate processes). This model is extremely efficient in terms of speed and server resources. However, it does mean that if any of your functions call synchronous methods that take a long time to complete, they will block not only the current request, but every other request being handled by your web application.

app.get("/", (req, res, next) => {
setTimeout(() => {
try {
throw new Error("AsynchronousException"); // You must catch and propagate yourself.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error("AsynchronousException"); // You must catch and propagate yourself.
// You must catch and propagate this error yourself
throw new Error("AsynchronousException");

router.get("/about", (req, res, next) => {
About.find({}).exec((err, queryResults) => {
if (err) {
return next(err);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return next(err);
// Propagate the error
return next(err);

@@ -112,7 +108,7 @@ Generally speaking, you should select a templating engine that delivers all the
> [!NOTE]
> There are many resources on the Internet to help you compare the different options!

For this project, we'll use the [Pug](https://pugjs.org/api/getting-started.html) templating engine (this is the recently-renamed Jade engine), as this is one of the most popular Express/JavaScript templating languages and is supported out of the box by the generator.
For this project, we'll use the [Pug](https://pugjs.org/api/getting-started.html) templating engine (this used to be called "Jade"), as this is one of the most popular Express/JavaScript templating languages and is supported out of the box by the generator.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For this project, we'll use the [Pug](https://pugjs.org/api/getting-started.html) templating engine (this used to be called "Jade"), as this is one of the most popular Express/JavaScript templating languages and is supported out of the box by the generator.
For this project, we'll use the [Pug](https://pugjs.org/api/getting-started.html) templating engine (previously called "Jade"), as this is one of the most popular Express/JavaScript templating languages and is supported out of the box by the generator.

}
```

Because the tool isn't installed globally we can't launch it from the command line (unless we add it to the path) but we can call it from an npm script because npm knows all about the installed packages. Find the `scripts` section of your package.json. Initially, it will contain one line, which begins with `"start"`. Update it by putting a comma at the end of that line, and adding the `"devstart"` and `"serverstart"` lines:
Because the tool isn't installed globally we can't launch it from the command line (unless we add it to the path) but we can call it from an npm script because npm knows all about the installed packages. Find the `scripts` section of your **package.json**. Initially, it will contain one line, which begins with `"start"`. Update it by putting a comma at the end of that line, and adding the `"devstart"` and `"serverstart"` lines:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Because the tool isn't installed globally we can't launch it from the command line (unless we add it to the path) but we can call it from an npm script because npm knows all about the installed packages. Find the `scripts` section of your **package.json**. Initially, it will contain one line, which begins with `"start"`. Update it by putting a comma at the end of that line, and adding the `"devstart"` and `"serverstart"` lines:
Because the tool isn't installed globally, we can't launch it from the command line (unless we add it to the path). However, we can call it from an npm script because npm knows which packages are installed. Find the `scripts` section of your **package.json**. Initially, it will contain one line, which begins with `"start"`. Update it by putting a comma at the end of that line, and adding the `"devstart"` and `"serverstart"` lines:

@@ -345,11 +342,12 @@ The **package.json** file defines the application dependencies and other informa
"debug": "~2.6.9",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need a jade dependency if it is now called pug?

Copy link
Contributor

This pull request has merge conflicts that must be resolved before it can be merged.

@github-actions github-actions bot added the merge conflicts 🚧 [PR only] label Jul 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Content:Learn Learning area docs merge conflicts 🚧 [PR only] size/l [PR only] 501-1000 LoC changed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Express tutorial needs upgrade to v5
2 participants