Skip to content

Commit 8443cb3

Browse files
Update Express tutorial to v5 (#40247)
* Update Express tutorial to v5 * Bit more work. WOrking on routes now * Routing parameters * Route functions done * Getting data chap 5 done * Remaining fixes for update to v5 * Apply suggestions from code review Co-authored-by: Chris Mills <[email protected]> * skeleton - fix up generated package.json * typo * Update index.md arrge --------- Co-authored-by: Chris Mills <[email protected]>
1 parent b8f0c90 commit 8443cb3

File tree

23 files changed

+325
-321
lines changed

23 files changed

+325
-321
lines changed

files/en-us/learn_web_development/extensions/server-side/express_nodejs/deployment/index.md

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ The server computer could be located on your premises and connected to the Inter
5555
This sort of remotely accessible computing/networking hardware is referred to as _Infrastructure as a Service (IaaS)_. Many IaaS vendors provide options to preinstall a particular operating system, onto which you must install the other components of your production environment. Other vendors allow you to select more fully-featured environments, perhaps including a complete Node setup.
5656

5757
> [!NOTE]
58-
> Pre-built environments can make setting up your website easier because they reduce the configuration, but the available options may limit you to an unfamiliar server (or other components) and may be based on an older version of the OS. Often it is better to install components yourself so that you get the ones that you want, and when you need to upgrade parts of the system, you have some idea of where to start!
58+
> Pre-built environments can make setting up your website easier because they reduce the required configuration, but the available options may limit you to an unfamiliar server (or other components) and may be based on an older version of the OS. Often it is better to install components yourself so that you get the ones that you want, and when you need to upgrade parts of the system, you have some idea of where to start!
5959
6060
Other hosting providers support Express as part of a _Platform as a Service_ (_PaaS_) offering. When using this sort of hosting you don't need to worry about most of your production environment (servers, load balancers, etc.) as the host platform takes care of those for you. That makes deployment quite straightforward because you just need to concentrate on your web application and not any other server infrastructure.
6161

@@ -107,16 +107,16 @@ In the following subsections, we outline the most important changes that you sho
107107
108108
### Database configuration
109109

110-
So far in this tutorial, we've used a single development database, for which the address and credentials are hard-coded into **app.js**.
110+
So far in this tutorial, we've used a single development database, for which the address and credentials were [hard-coded into **bin/www**](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/mongoose#connect_to_mongodb).
111111
Since the development database doesn't contain any information that we mind being exposed or corrupted, there is no particular risk in leaking these details.
112-
However if you're working with real data, in particular personal user information, then protecting your database credentials is very important.
112+
However if you're working with real data, in particular personal user information, then it is very important to protect your database credentials.
113113

114114
For this reason we want to use a different database for production than we use for development, and also keep the production database credentials separate from the source code so that they can be properly protected.
115115

116116
If your hosting provider supports setting environment variables through a web interface (as many do), one way to do this is to have the server get the database URL from an environment variable.
117117
Below we modify the LocalLibrary website to get the database URI from an OS environment variable, if it has been defined, and otherwise use the development database URL.
118118

119-
Open **app.js** and find the line that sets the MongoDB connection variable.
119+
Open **bin.www** and find the line that sets the MongoDB connection variable.
120120
It will look something like this:
121121

122122
```js
@@ -127,19 +127,9 @@ const mongoDB =
127127
Replace the line with the following code that uses `process.env.MONGODB_URI` to get the connection string from an environment variable named `MONGODB_URI` if has been set (use your own database URL instead of the placeholder below).
128128

129129
```js
130-
// Set up mongoose connection
131-
const mongoose = require("mongoose");
132-
133-
mongoose.set("strictQuery", false);
134-
135130
const dev_db_url =
136131
"mongodb+srv://your_user_name:[email protected]/local_library?retryWrites=true&w=majority";
137132
const mongoDB = process.env.MONGODB_URI || dev_db_url;
138-
139-
main().catch((err) => console.log(err));
140-
async function main() {
141-
await mongoose.connect(mongoDB);
142-
}
143133
```
144134

145135
> [!NOTE]
@@ -166,7 +156,7 @@ The debug variable is declared with the name 'author', and the prefix "author" w
166156
const debug = require("debug")("author");
167157

168158
// Display Author update form on GET.
169-
exports.author_update_get = asyncHandler(async (req, res, next) => {
159+
exports.author_update_get = async (req, res, next) => {
170160
const author = await Author.findById(req.params.id).exec();
171161
if (author === null) {
172162
// No results.
@@ -177,7 +167,7 @@ exports.author_update_get = asyncHandler(async (req, res, next) => {
177167
}
178168

179169
res.render("author_form", { title: "Update Author", author });
180-
});
170+
};
181171
```
182172

183173
You can then enable a particular set of logs by specifying them as a comma-separated list in the `DEBUG` environment variable.
@@ -256,7 +246,7 @@ const app = express();
256246
app.use(
257247
helmet.contentSecurityPolicy({
258248
directives: {
259-
"script-src": ["'self'", "code.jquery.com", "cdn.jsdelivr.net"],
249+
"script-src": ["'self'", "cdn.jsdelivr.net"],
260250
},
261251
}),
262252
);
@@ -265,7 +255,7 @@ app.use(
265255
```
266256

267257
We normally might have just inserted `app.use(helmet());` to add the _subset_ of the security-related headers that make sense for most sites.
268-
However in the [LocalLibrary base template](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template) we include some bootstrap and jQuery scripts.
258+
However in the [LocalLibrary base template](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template) we include some bootstrap scripts.
269259
These violate the helmet's _default_ [Content Security Policy (CSP)](/en-US/docs/Web/HTTP/Guides/CSP), which does not allow loading of cross-site scripts.
270260
To allow these scripts to be loaded we modify the helmet configuration so that it sets CSP directives to allow script loading from the indicated domains.
271261
For your own server you can add/disable specific headers as needed by following the [instructions for using helmet here](https://www.npmjs.com/package/helmet).
@@ -325,7 +315,7 @@ Open **package.json**, and add this information as an **engines > node** as show
325315

326316
```json
327317
"engines": {
328-
"node": ">=16.17.1"
318+
"node": ">=22.0.0"
329319
},
330320
```
331321

files/en-us/learn_web_development/extensions/server-side/express_nodejs/development_environment/index.md

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ sidebar: learnsidebar
1010

1111
Now that you know what [Express](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction#introducing_express) is for, we'll show you how to set up and test a Node/Express development environment on Windows, or Linux (Ubuntu), or macOS. For any of those operating systems, this article provides what you need to start developing Express apps.
1212

13-
> [!WARNING]
14-
> The Express tutorial is written for Express version 4, while the latest version is Express 5.
15-
> We plan to update the documentation to support Express 5 in the second half of 2025. Until then, we have updated the installation commands so they install Express 4 rather than the latest version, to avoid any potential compatibility problems.
16-
1713
<table>
1814
<tbody>
1915
<tr>
@@ -58,7 +54,7 @@ There are many [releases of Node](https://nodejs.org/en/blog/release/) — newer
5854

5955
Generally you should use the most recent _LTS (long-term supported)_ release as this will be more stable than the "current" release while still having relatively recent features (and is still being actively maintained). You should use the _Current_ release if you need a feature that is not present in the LTS version.
6056

61-
For _Express_ you should always use the latest version.
57+
For _Express_ you should use the most recent LTS release of Node.
6258

6359
### What about databases and other dependencies?
6460

@@ -85,11 +81,11 @@ After `nvm-windows` has installed, open a command prompt (or PowerShell) and ent
8581
nvm install lts
8682
```
8783

88-
At time of writing the LTS version of nodejs is 20.11.0.
84+
At time of writing the LTS version of nodejs is 22.17.0.
8985
You can set this as the _current version_ to use with the command below:
9086

9187
```bash
92-
nvm use 20.11.0
88+
nvm use 22.17.0
9389
```
9490

9591
> [!NOTE]
@@ -109,12 +105,12 @@ After `nvm` has installed, open a terminal enter the following command to downlo
109105
nvm install --lts
110106
```
111107

112-
At the time of writing, the LTS version of nodejs is 20.11.0.
108+
At the time of writing, the LTS version of nodejs is 22.17.0.
113109
The command `nvm list` shows the downloaded set of version and the current version.
114110
You can set a particular version as the _current version_ with the command below (the same as for `nvm-windows`)
115111

116112
```bash
117-
nvm use 20.11.0
113+
nvm use 22.17.0
118114
```
119115

120116
Use the command `nvm --help` to find out other command line options.
@@ -127,14 +123,14 @@ A good way to do this is to use the "version" command in your terminal/command p
127123

128124
```bash
129125
> node -v
130-
v20.11.0
126+
v22.17.0
131127
```
132128

133129
The _Nodejs_ package manager _npm_ should also have been installed, and can be tested in the same way:
134130

135131
```bash
136132
> npm -v
137-
10.2.4
133+
10.9.2
138134
```
139135

140136
As a slightly more exciting test let's create a very basic "pure node" server that prints out "Hello World" in the browser when you visit the correct URL in your browser:
@@ -217,20 +213,20 @@ The following steps show how you can use npm to download a package, save it into
217213
{
218214
"name": "myapp",
219215
"version": "1.0.0",
220-
"description": "",
221216
"main": "index.js",
222217
"scripts": {
223218
"test": "echo \"Error: no test specified\" && exit 1"
224219
},
225220
"author": "",
226-
"license": "ISC"
221+
"license": "ISC",
222+
"description": ""
227223
}
228224
```
229225

230226
3. Now install Express in the `myapp` directory and save it in the dependencies list of your **package.json** file:
231227

232228
```bash
233-
npm install express@^4.21.2
229+
npm install express
234230
```
235231

236232
The dependencies section of your **package.json** will now appear at the end of the **package.json** file and will include _Express_.
@@ -247,7 +243,7 @@ The following steps show how you can use npm to download a package, save it into
247243
"author": "",
248244
"license": "ISC",
249245
"dependencies": {
250-
"express": "^4.21.2"
246+
"express": "^5.1.0"
251247
}
252248
}
253249
```
@@ -303,9 +299,9 @@ npm install eslint --save-dev
303299
The following entry would then be added to your application's **package.json**:
304300

305301
```json
306-
"devDependencies": {
307-
"eslint": "^7.10.0"
308-
}
302+
"devDependencies": {
303+
"eslint": "^9.30.1"
304+
}
309305
```
310306

311307
> [!NOTE]
@@ -318,7 +314,7 @@ In addition to defining and fetching dependencies you can also define _named_ sc
318314
> [!NOTE]
319315
> Task runners like [Gulp](https://gulpjs.com/) and [Grunt](https://gruntjs.com/) can also be used to run tests and other external tools.
320316
321-
For example, to define a script to run the _eslint_ development dependency that we specified in the previous section we might add the following script block to our **package.json** file (assuming that our application source is in a folder /src/js):
317+
For example, to define a script to run the _eslint_ development dependency that we specified in the previous section we might add the following script block to our **package.json** file (assuming that our application source is in a folder `/src/js`):
322318

323319
```json
324320
"scripts": {

files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_detail_page/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The author detail page needs to display the information about the specified `Aut
1111

1212
Open **/controllers/authorController.js**.
1313

14-
Add the following lines to the top of the file to `require()` the `Book` module needed by the author detail page (other modules such as "express-async-handler" should already be present).
14+
Add the following lines to the top of the file to `require()` the `Book` module needed by the author detail page.
1515

1616
```js
1717
const Book = require("../models/book");
@@ -21,7 +21,7 @@ Find the exported `author_detail()` controller method and replace it with the fo
2121

2222
```js
2323
// Display detail page for a specific Author.
24-
exports.author_detail = asyncHandler(async (req, res, next) => {
24+
exports.author_detail = async (req, res, next) => {
2525
// Get details of author and all their books (in parallel)
2626
const [author, allBooksByAuthor] = await Promise.all([
2727
Author.findById(req.params.id).exec(),
@@ -40,12 +40,12 @@ exports.author_detail = asyncHandler(async (req, res, next) => {
4040
author,
4141
author_books: allBooksByAuthor,
4242
});
43-
});
43+
};
4444
```
4545

4646
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).
4747
The route controller function uses `Promise.all()` to query the specified `Author` and their associated `Book` instances in parallel.
48-
If no matching author is found an Error object is sent to the Express error handling middleware.
48+
If no matching author is found, an `Error` object is sent to the Express error handling middleware.
4949
If the author is found then the retrieved database information is rendered using the "author_detail" template.
5050

5151
## View

files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_list_page/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ Open **/controllers/authorController.js**. Find the exported `author_list()` con
1515

1616
```js
1717
// Display list of all Authors.
18-
exports.author_list = asyncHandler(async (req, res, next) => {
18+
exports.author_list = async (req, res, next) => {
1919
const allAuthors = await Author.find().sort({ family_name: 1 }).exec();
2020
res.render("author_list", {
2121
title: "Author List",
2222
author_list: allAuthors,
2323
});
24-
});
24+
};
2525
```
2626

2727
The route controller function follows the same pattern as for the other list pages.
@@ -74,7 +74,7 @@ The genre list controller function needs to get a list of all `Genre` instances,
7474
- Sort the results by name, in ascending order.
7575

7676
3. The template to be rendered should be named **genre_list.pug**.
77-
4. The template to be rendered should be passed the variables `title` ('Genre List') and `genre_list` (the list of genres returned from your `Genre.find()` callback).
77+
4. The template to be rendered should be passed the variables `title` ('Genre List') and `genre_list` (the list of genres returned from `Genre.find()`).
7878
5. The view should match the screenshot/requirements above (this should have a very similar structure/format to the Author list view, except for the fact that genres do not have dates).
7979

8080
## Next steps

files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_detail_page/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Open **/controllers/bookController.js**. Find the exported `book_detail()` contr
1313

1414
```js
1515
// Display detail page for a specific book.
16-
exports.book_detail = asyncHandler(async (req, res, next) => {
16+
exports.book_detail = async (req, res, next) => {
1717
// Get details of books, book instances for specific book
1818
const [book, bookInstances] = await Promise.all([
1919
Book.findById(req.params.id).populate("author").populate("genre").exec(),
@@ -32,15 +32,15 @@ exports.book_detail = asyncHandler(async (req, res, next) => {
3232
book,
3333
book_instances: bookInstances,
3434
});
35-
});
35+
};
3636
```
3737

3838
> [!NOTE]
3939
> 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.
4040
4141
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).
4242
The route controller function uses `Promise.all()` to query the specified `Book` and its associated copies (`BookInstance`) in parallel.
43-
If no matching book is found an Error object is returned with a "404: Not Found" error.
43+
If no matching book is found, an `Error` object is returned with a "404: Not Found" error.
4444
If the book is found, then the retrieved database information is rendered using the "book_detail" template.
4545
Since the key 'title' is used to give name to the webpage (as defined in the header in 'layout.pug'), this time we are passing `results.book.title` while rendering the webpage.
4646

files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_list_page/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ Open **/controllers/bookController.js**. Find the exported `book_list()` control
1515

1616
```js
1717
// Display list of all books.
18-
exports.book_list = asyncHandler(async (req, res, next) => {
18+
exports.book_list = async (req, res, next) => {
1919
const allBooks = await Book.find({}, "title author")
2020
.sort({ title: 1 })
2121
.populate("author")
2222
.exec();
2323

2424
res.render("book_list", { title: "Book List", book_list: allBooks });
25-
});
25+
};
2626
```
2727

2828
The route handler calls the `find()` function on the `Book` model, selecting to return only the `title` and `author` as we don't need the other fields (it will also return the `_id` and virtual fields), and sorting the results by the title alphabetically using the `sort()` method.

files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Find the exported `bookinstance_detail()` controller method and replace it with
1616

1717
```js
1818
// Display detail page for a specific BookInstance.
19-
exports.bookinstance_detail = asyncHandler(async (req, res, next) => {
19+
exports.bookinstance_detail = async (req, res, next) => {
2020
const bookInstance = await BookInstance.findById(req.params.id)
2121
.populate("book")
2222
.exec();
@@ -32,7 +32,7 @@ exports.bookinstance_detail = asyncHandler(async (req, res, next) => {
3232
title: "Book:",
3333
bookinstance: bookInstance,
3434
});
35-
});
35+
};
3636
```
3737

3838
The implementation is very similar to that used for the other model detail pages.

files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ Find the exported `bookinstance_list()` controller method and replace it with th
1616

1717
```js
1818
// Display list of all BookInstances.
19-
exports.bookinstance_list = asyncHandler(async (req, res, next) => {
19+
exports.bookinstance_list = async (req, res, next) => {
2020
const allBookInstances = await BookInstance.find().populate("book").exec();
2121

2222
res.render("bookinstance_list", {
2323
title: "Book Instance List",
2424
bookinstance_list: allBookInstances,
2525
});
26-
});
26+
};
2727
```
2828

2929
The route handler calls the `find()` function on the `BookInstance` model, and then daisy-chains a call to `populate()` with the `book` field—this will replace the book id stored for each `BookInstance` with a full `Book` document.
@@ -62,7 +62,7 @@ block content
6262
p There are no book copies in this library.
6363
```
6464

65-
This view is much the same as all the others. It extends the layout, replacing the _content_ block, displays the `title` passed in from the controller, and iterates through all the book copies in `bookinstance_list`. For each copy we display its status (color coded) and if the book is not available, its expected return date. One new feature is introduced—we can use dot notation after a tag to assign a class. So `span.text-success` will be compiled to `<span class="text-success">` (and might also be written in Pug as `span(class="text-success")`.
65+
This view is much the same as all the others. It extends the layout, replacing the _content_ block, displays the `title` passed in from the controller, and iterates through all the book copies in `bookinstance_list`. For each copy we display its status (color coded) and if the book is not available, its expected return date. One new feature is introduced—we can use dot notation after a tag to assign a class. So `span.text-success` will be compiled to `<span class="text-success">` (and might also be written in Pug as `span(class="text-success")`).
6666

6767
## What does it look like?
6868

0 commit comments

Comments
 (0)