diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/deployment/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/deployment/index.md
index 8835bfc299136df..99fb654c75050d7 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/deployment/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/deployment/index.md
@@ -55,7 +55,7 @@ The server computer could be located on your premises and connected to the Inter
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.
> [!NOTE]
-> 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!
+> 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!
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.
@@ -107,16 +107,16 @@ In the following subsections, we outline the most important changes that you sho
### Database configuration
-So far in this tutorial, we've used a single development database, for which the address and credentials are hard-coded into **app.js**.
+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).
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.
-However if you're working with real data, in particular personal user information, then protecting your database credentials is very important.
+However if you're working with real data, in particular personal user information, then it is very important to protect your database credentials.
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.
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.
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.
-Open **app.js** and find the line that sets the MongoDB connection variable.
+Open **bin.www** and find the line that sets the MongoDB connection variable.
It will look something like this:
```js
@@ -127,19 +127,9 @@ const mongoDB =
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).
```js
-// Set up mongoose connection
-const mongoose = require("mongoose");
-
-mongoose.set("strictQuery", false);
-
const dev_db_url =
"mongodb+srv://your_user_name:your_password@cluster0.cojoign.mongodb.net/local_library?retryWrites=true&w=majority";
const mongoDB = process.env.MONGODB_URI || dev_db_url;
-
-main().catch((err) => console.log(err));
-async function main() {
- await mongoose.connect(mongoDB);
-}
```
> [!NOTE]
@@ -166,7 +156,7 @@ The debug variable is declared with the name 'author', and the prefix "author" w
const debug = require("debug")("author");
// Display Author update form on GET.
-exports.author_update_get = asyncHandler(async (req, res, next) => {
+exports.author_update_get = async (req, res, next) => {
const author = await Author.findById(req.params.id).exec();
if (author === null) {
// No results.
@@ -177,7 +167,7 @@ exports.author_update_get = asyncHandler(async (req, res, next) => {
}
res.render("author_form", { title: "Update Author", author });
-});
+};
```
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();
app.use(
helmet.contentSecurityPolicy({
directives: {
- "script-src": ["'self'", "code.jquery.com", "cdn.jsdelivr.net"],
+ "script-src": ["'self'", "cdn.jsdelivr.net"],
},
}),
);
@@ -265,7 +255,7 @@ app.use(
```
We normally might have just inserted `app.use(helmet());` to add the _subset_ of the security-related headers that make sense for most sites.
-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.
+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.
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.
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.
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
```json
"engines": {
- "node": ">=16.17.1"
+ "node": ">=22.0.0"
},
```
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/development_environment/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/development_environment/index.md
index 1ab2bed5ef5d7bb..d1dadd972623fde 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/development_environment/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/development_environment/index.md
@@ -10,10 +10,6 @@ sidebar: learnsidebar
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.
-> [!WARNING]
-> The Express tutorial is written for Express version 4, while the latest version is Express 5.
-> 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.
-
@@ -58,7 +54,7 @@ There are many [releases of Node](https://nodejs.org/en/blog/release/) — newer
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.
-For _Express_ you should always use the latest version.
+For _Express_ you should use the most recent LTS release of Node.
### What about databases and other dependencies?
@@ -85,11 +81,11 @@ After `nvm-windows` has installed, open a command prompt (or PowerShell) and ent
nvm install lts
```
-At time of writing the LTS version of nodejs is 20.11.0.
+At time of writing the LTS version of nodejs is 22.17.0.
You can set this as the _current version_ to use with the command below:
```bash
-nvm use 20.11.0
+nvm use 22.17.0
```
> [!NOTE]
@@ -109,12 +105,12 @@ After `nvm` has installed, open a terminal enter the following command to downlo
nvm install --lts
```
-At the time of writing, the LTS version of nodejs is 20.11.0.
+At the time of writing, the LTS version of nodejs is 22.17.0.
The command `nvm list` shows the downloaded set of version and the current version.
You can set a particular version as the _current version_ with the command below (the same as for `nvm-windows`)
```bash
-nvm use 20.11.0
+nvm use 22.17.0
```
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
```bash
> node -v
-v20.11.0
+v22.17.0
```
The _Nodejs_ package manager _npm_ should also have been installed, and can be tested in the same way:
```bash
> npm -v
-10.2.4
+10.9.2
```
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
{
"name": "myapp",
"version": "1.0.0",
- "description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
- "license": "ISC"
+ "license": "ISC",
+ "description": ""
}
```
3. Now install Express in the `myapp` directory and save it in the dependencies list of your **package.json** file:
```bash
- npm install express@^4.21.2
+ npm install express
```
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
"author": "",
"license": "ISC",
"dependencies": {
- "express": "^4.21.2"
+ "express": "^5.1.0"
}
}
```
@@ -303,9 +299,9 @@ npm install eslint --save-dev
The following entry would then be added to your application's **package.json**:
```json
- "devDependencies": {
- "eslint": "^7.10.0"
- }
+"devDependencies": {
+ "eslint": "^9.30.1"
+}
```
> [!NOTE]
@@ -318,7 +314,7 @@ In addition to defining and fetching dependencies you can also define _named_ sc
> [!NOTE]
> Task runners like [Gulp](https://gulpjs.com/) and [Grunt](https://gruntjs.com/) can also be used to run tests and other external tools.
-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):
+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`):
```json
"scripts": {
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_detail_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_detail_page/index.md
index f27a3f101bb7c5a..de23ae58eac6677 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_detail_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_detail_page/index.md
@@ -11,7 +11,7 @@ The author detail page needs to display the information about the specified `Aut
Open **/controllers/authorController.js**.
-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).
+Add the following lines to the top of the file to `require()` the `Book` module needed by the author detail page.
```js
const Book = require("../models/book");
@@ -21,7 +21,7 @@ Find the exported `author_detail()` controller method and replace it with the fo
```js
// Display detail page for a specific Author.
-exports.author_detail = asyncHandler(async (req, res, next) => {
+exports.author_detail = async (req, res, next) => {
// Get details of author and all their books (in parallel)
const [author, allBooksByAuthor] = await Promise.all([
Author.findById(req.params.id).exec(),
@@ -40,12 +40,12 @@ exports.author_detail = asyncHandler(async (req, res, next) => {
author,
author_books: allBooksByAuthor,
});
-});
+};
```
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.
If the author is found then the retrieved database information is rendered using the "author_detail" template.
## View
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_list_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_list_page/index.md
index e38c220cbe9d398..eeda88500df4351 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_list_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/author_list_page/index.md
@@ -15,13 +15,13 @@ Open **/controllers/authorController.js**. Find the exported `author_list()` con
```js
// Display list of all Authors.
-exports.author_list = asyncHandler(async (req, res, next) => {
+exports.author_list = async (req, res, next) => {
const allAuthors = await Author.find().sort({ family_name: 1 }).exec();
res.render("author_list", {
title: "Author List",
author_list: allAuthors,
});
-});
+};
```
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,
- Sort the results by name, in ascending order.
3. The template to be rendered should be named **genre_list.pug**.
-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).
+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()`).
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).
## Next steps
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_detail_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_detail_page/index.md
index 6f5b84e9b0821e0..67008dba5ebbc9e 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_detail_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_detail_page/index.md
@@ -13,7 +13,7 @@ Open **/controllers/bookController.js**. Find the exported `book_detail()` contr
```js
// Display detail page for a specific book.
-exports.book_detail = asyncHandler(async (req, res, next) => {
+exports.book_detail = async (req, res, next) => {
// Get details of books, book instances for specific book
const [book, bookInstances] = await Promise.all([
Book.findById(req.params.id).populate("author").populate("genre").exec(),
@@ -32,7 +32,7 @@ exports.book_detail = asyncHandler(async (req, res, next) => {
book,
book_instances: bookInstances,
});
-});
+};
```
> [!NOTE]
@@ -40,7 +40,7 @@ exports.book_detail = asyncHandler(async (req, res, next) => {
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.
If the book is found, then the retrieved database information is rendered using the "book_detail" template.
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.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_list_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_list_page/index.md
index 8e5b7c22103f15d..e333786ab3a5213 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_list_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/book_list_page/index.md
@@ -15,14 +15,14 @@ Open **/controllers/bookController.js**. Find the exported `book_list()` control
```js
// Display list of all books.
-exports.book_list = asyncHandler(async (req, res, next) => {
+exports.book_list = async (req, res, next) => {
const allBooks = await Book.find({}, "title author")
.sort({ title: 1 })
.populate("author")
.exec();
res.render("book_list", { title: "Book List", book_list: allBooks });
-});
+};
```
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.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.md
index 483b5730776a676..dda41e91c3160e7 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.md
@@ -16,7 +16,7 @@ Find the exported `bookinstance_detail()` controller method and replace it with
```js
// Display detail page for a specific BookInstance.
-exports.bookinstance_detail = asyncHandler(async (req, res, next) => {
+exports.bookinstance_detail = async (req, res, next) => {
const bookInstance = await BookInstance.findById(req.params.id)
.populate("book")
.exec();
@@ -32,7 +32,7 @@ exports.bookinstance_detail = asyncHandler(async (req, res, next) => {
title: "Book:",
bookinstance: bookInstance,
});
-});
+};
```
The implementation is very similar to that used for the other model detail pages.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.md
index af629fe03663e4e..2a61f1218a2e7da 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.md
@@ -16,14 +16,14 @@ Find the exported `bookinstance_list()` controller method and replace it with th
```js
// Display list of all BookInstances.
-exports.bookinstance_list = asyncHandler(async (req, res, next) => {
+exports.bookinstance_list = async (req, res, next) => {
const allBookInstances = await BookInstance.find().populate("book").exec();
res.render("bookinstance_list", {
title: "Book Instance List",
bookinstance_list: allBookInstances,
});
-});
+};
```
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
p There are no book copies in this library.
```
-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 `` (and might also be written in Pug as `span(class="text-success")`.
+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 `` (and might also be written in Pug as `span(class="text-success")`).
## What does it look like?
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/genre_detail_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/genre_detail_page/index.md
index 3538580c6abd72d..8069c5b437bccb6 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/genre_detail_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/genre_detail_page/index.md
@@ -13,7 +13,7 @@ The page should display the genre name and a list of all books in the genre with
## Controller
-Open **/controllers/genreController.js** and require the `Book` module at the top of the file (the file should already `require()` the `Genre` module and "express-async-handler").
+Open **/controllers/genreController.js** and require the `Book` module at the top of the file (the file should already `require()` the `Genre` module).
```js
const Book = require("../models/book");
@@ -23,7 +23,7 @@ Find the exported `genre_detail()` controller method and replace it with the fol
```js
// Display detail page for a specific Genre.
-exports.genre_detail = asyncHandler(async (req, res, next) => {
+exports.genre_detail = async (req, res, next) => {
// Get details of genre and all associated books (in parallel)
const [genre, booksInGenre] = await Promise.all([
Genre.findById(req.params.id).exec(),
@@ -41,7 +41,7 @@ exports.genre_detail = asyncHandler(async (req, res, next) => {
genre,
genre_books: booksInGenre,
});
-});
+};
```
We first use `Genre.findById()` to get Genre information for a specific ID, and `Book.find()` to get all books records that have that same associated genre ID.
@@ -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)).
If the `genre` is found, then we call `render()` to display the view.
The view template is **genre_detail** (.pug).
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/home_page/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/home_page/index.md
index cc635212a20c5c4..76946fe2528067d 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/home_page/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/home_page/index.md
@@ -26,9 +26,9 @@ router.get("/", book_controller.index); // This actually maps to /catalog/ becau
The book controller index function passed as a parameter (`book_controller.index`) has a "placeholder" implementation defined in **/controllers/bookController.js**:
```js
-exports.index = asyncHandler(async (req, res, next) => {
+exports.index = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Site Home Page");
-});
+};
```
It is this controller function that we extend to get information from our models and then render it using a template (view).
@@ -41,17 +41,15 @@ Open **/controllers/bookController.js**. Near the top of the file you should see
```js
const Book = require("../models/book");
-const asyncHandler = require("express-async-handler");
-exports.index = asyncHandler(async (req, res, next) => {
+exports.index = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Site Home Page");
-});
+};
```
Replace all the code above with the following code fragment.
The first thing this does is import (`require()`) all the models.
We need to do this because we'll be using them to get our counts of documents.
-The code also requires "express-async-handler", which provides a wrapper to [catch exceptions thrown in route handler functions](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes#handling_exceptions_in_route_functions).
```js
const Book = require("../models/book");
@@ -59,9 +57,7 @@ const Author = require("../models/author");
const Genre = require("../models/genre");
const BookInstance = require("../models/bookinstance");
-const asyncHandler = require("express-async-handler");
-
-exports.index = asyncHandler(async (req, res, next) => {
+exports.index = async (req, res, next) => {
// Get details of books, book instances, authors and genre counts (in parallel)
const [
numBooks,
@@ -85,7 +81,7 @@ exports.index = asyncHandler(async (req, res, next) => {
author_count: numAuthors,
genre_count: numGenres,
});
-});
+};
```
We use the [`countDocuments()`]() method to get the number of instances of each model.
@@ -96,7 +92,7 @@ Because the queries for document counts are independent of each other we use [`P
The method returns a new promise that we [`await`](/en-US/docs/Web/JavaScript/Reference/Operators/await) for completion (execution pauses within _this function_ at `await`).
When all the queries complete, the promise returned by `all()` fulfills, continuing execution of the route handler function, and populating the array with the results of the database queries.
-We then call [`res.render()`](https://expressjs.com/en/4x/api.html#res.render), specifying a view (template) named '**index**' and objects mapping the results of the database queries to the view template.
+We then call [`res.render()`](https://expressjs.com/en/5x/api.html#res.render), specifying a view (template) named '**index**' and objects mapping the results of the database queries to the view template.
The data is supplied as key-value pairs, and can be accessed in the template using the key.
> [!NOTE]
@@ -104,7 +100,7 @@ The data is supplied as key-value pairs, and can be accessed in the template usi
> Other template languages may require that you pass in values for all objects that you use.
Note that the code is very simple because we can assume that the database queries succeed.
-If any of the database operations fail, the exception that is thrown will be caught by `asyncHandler()` and passed to the `next` middleware handler in the chain.
+If any of the database operations fail, the exception that is thrown will cause the Promise to reject, and Express will pass the error to the `next` middleware handler in the chain.
## View
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.md
index 55c5bf0fcddefd4..901c15d2fe9fbd5 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.md
@@ -16,9 +16,8 @@ html(lang='en')
title= title
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
- link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css", integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N", crossorigin="anonymous")
- script(src="https://code.jquery.com/jquery-3.5.1.slim.min.js", integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj", crossorigin="anonymous")
- script(src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js", integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+", crossorigin="anonymous")
+ link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css", integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr", crossorigin="anonymous")
+ script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js", integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q", crossorigin="anonymous")
link(rel='stylesheet', href='/stylesheets/style.css')
body
div(class='container-fluid')
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_author_form/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_author_form/index.md
index bd76172cd5d6af4..36f0fe2a6b0b160 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_author_form/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_author_form/index.md
@@ -60,7 +60,7 @@ exports.author_create_post = [
.toDate(),
// Process request after validation and sanitization.
- asyncHandler(async (req, res, next) => {
+ async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -81,13 +81,12 @@ exports.author_create_post = [
});
return;
}
- // Data from form is valid.
- // Save author.
+ // Data from form is valid.
+ // Save and redirect to new author record.
await author.save();
- // Redirect to new author record.
res.redirect(author.url);
- }),
+ },
];
```
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_book_form/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_book_form/index.md
index e46399e96107d4c..2efbe9242c0b88e 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_book_form/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_book_form/index.md
@@ -21,7 +21,7 @@ Find the exported `book_create_get()` controller method and replace it with the
```js
// Display book create form on GET.
-exports.book_create_get = asyncHandler(async (req, res, next) => {
+exports.book_create_get = async (req, res, next) => {
// Get all authors and genres, which we can use for adding to our book.
const [allAuthors, allGenres] = await Promise.all([
Author.find().sort({ family_name: 1 }).exec(),
@@ -33,7 +33,7 @@ exports.book_create_get = asyncHandler(async (req, res, next) => {
authors: allAuthors,
genres: allGenres,
});
-});
+};
```
This uses `await` on the result of `Promise.all()` to get all `Author` and `Genre` objects in parallel (the same approach used in [Express Tutorial Part 5: Displaying library data](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Displaying_data)).
@@ -72,7 +72,7 @@ exports.book_create_post = [
body("genre.*").escape(),
// Process request after validation and sanitization.
- asyncHandler(async (req, res, next) => {
+ async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -107,12 +107,13 @@ exports.book_create_post = [
book,
errors: errors.array(),
});
- } else {
- // Data from form is valid. Save book.
- await book.save();
- res.redirect(book.url);
+ return;
}
- }),
+
+ // Data from form is valid. Save book.
+ await book.save();
+ res.redirect(book.url);
+ },
];
```
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_bookinstance_form/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_bookinstance_form/index.md
index d134bbb7116f81e..4633a753d198e9d 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_bookinstance_form/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_bookinstance_form/index.md
@@ -28,14 +28,14 @@ Find the exported `bookinstance_create_get()` controller method and replace it w
```js
// Display BookInstance create form on GET.
-exports.bookinstance_create_get = asyncHandler(async (req, res, next) => {
+exports.bookinstance_create_get = async (req, res, next) => {
const allBooks = await Book.find({}, "title").sort({ title: 1 }).exec();
res.render("bookinstance_form", {
title: "Create BookInstance",
book_list: allBooks,
});
-});
+};
```
The controller gets a sorted list of all books (`allBooks`) and passes it via `book_list` to the view **`bookinstance_form.pug`** (along with a `title`).
@@ -62,7 +62,7 @@ exports.bookinstance_create_post = [
.toDate(),
// Process request after validation and sanitization.
- asyncHandler(async (req, res, next) => {
+ async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -88,10 +88,11 @@ exports.bookinstance_create_post = [
});
return;
}
+
// Data from form is valid
await bookInstance.save();
res.redirect(bookInstance.url);
- }),
+ },
];
```
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_genre_form/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_genre_form/index.md
index 2013c1c24b4f66c..52985b810e8846e 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_genre_form/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/create_genre_form/index.md
@@ -31,9 +31,6 @@ exports.genre_create_get = (req, res, next) => {
};
```
-Note that this replaces the placeholder asynchronous handler that we added in the [Express Tutorial Part 4: Routes and controllers](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/routes#genre_controller) with a "normal" express route handler function.
-We don't need the `asyncHandler()` wrapper for this route, because it doesn't contain any code that can throw an exception.
-
## Controller—post route
Find the exported `genre_create_post()` controller method and replace it with the following code.
@@ -48,7 +45,7 @@ exports.genre_create_post = [
.escape(),
// Process request after validation and sanitization.
- asyncHandler(async (req, res, next) => {
+ async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -64,6 +61,7 @@ exports.genre_create_post = [
});
return;
}
+
// Data from form is valid.
// Check if Genre with same name already exists.
const genreExists = await Genre.findOne({ name: req.body.name })
@@ -72,12 +70,13 @@ exports.genre_create_post = [
if (genreExists) {
// Genre exists, redirect to its detail page.
res.redirect(genreExists.url);
- } else {
- await genre.save();
- // New genre saved. Redirect to genre detail page.
- res.redirect(genre.url);
+ return;
}
- }),
+
+ // New genre. Save and redirect to its detail page.
+ await genre.save();
+ res.redirect(genre.url);
+ },
];
```
@@ -104,7 +103,7 @@ After specifying the validators we create a middleware function to extract any v
```js
// Process request after validation and sanitization.
-asyncHandler(async (req, res, next) => {
+async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -122,7 +121,7 @@ asyncHandler(async (req, res, next) => {
}
// Data from form is valid.
// …
-});
+};
```
If the genre name data is valid then we perform a case-insensitive search to see if a `Genre` with the same name already exists (as we don't want to create duplicate or near duplicate records that vary only in letter case, such as: "Fantasy", "fantasy", "FaNtAsY", and so on).
@@ -140,11 +139,11 @@ const genreExists = await Genre.findOne({ name: req.body.name })
if (genreExists) {
// Genre exists, redirect to its detail page.
res.redirect(genreExists.url);
-} else {
- await genre.save();
- // New genre saved. Redirect to genre detail page.
- res.redirect(genre.url);
}
+
+// New genre. Save and redirect to its detail page.
+await genre.save();
+res.redirect(genre.url);
```
This same pattern is used in all our post controllers: we run validators (with sanitizers), then check for errors and either re-render the form with error information or save the data.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/delete_author_form/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/delete_author_form/index.md
index 9b111fd462266a4..4d1469d835d2a17 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/delete_author_form/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/delete_author_form/index.md
@@ -17,7 +17,7 @@ Open **/controllers/authorController.js**. Find the exported `author_delete_get(
```js
// Display Author delete form on GET.
-exports.author_delete_get = asyncHandler(async (req, res, next) => {
+exports.author_delete_get = async (req, res, next) => {
// Get details of author and all their books (in parallel)
const [author, allBooksByAuthor] = await Promise.all([
Author.findById(req.params.id).exec(),
@@ -27,6 +27,7 @@ exports.author_delete_get = asyncHandler(async (req, res, next) => {
if (author === null) {
// No results.
res.redirect("/catalog/authors");
+ return;
}
res.render("author_delete", {
@@ -34,7 +35,7 @@ exports.author_delete_get = asyncHandler(async (req, res, next) => {
author,
author_books: allBooksByAuthor,
});
-});
+};
```
The controller gets the id of the `Author` instance to be deleted from the URL parameter (`req.params.id`).
@@ -49,6 +50,7 @@ When both operations have completed it renders the **author_delete.pug** view, p
> if (author === null) {
> // No results.
> res.redirect("/catalog/authors");
+> return;
> }
> ```
@@ -58,7 +60,7 @@ Find the exported `author_delete_post()` controller method, and replace it with
```js
// Handle Author delete on POST.
-exports.author_delete_post = asyncHandler(async (req, res, next) => {
+exports.author_delete_post = async (req, res, next) => {
// Get details of author and all their books (in parallel)
const [author, allBooksByAuthor] = await Promise.all([
Author.findById(req.params.id).exec(),
@@ -77,7 +79,7 @@ exports.author_delete_post = asyncHandler(async (req, res, next) => {
// Author has no books. Delete object and redirect to the list of authors.
await Author.findByIdAndDelete(req.body.authorid);
res.redirect("/catalog/authors");
-});
+};
```
First we validate that an id has been provided (this is sent via the form body parameters, rather than using the version in the URL).
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/index.md
index ed9bb4f08288aea..f7b6c4849c56d4a 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/index.md
@@ -165,7 +165,7 @@ The functions are defined as below:
- [`validationResult(req)`](https://express-validator.github.io/docs/api/validation-result/#validationresult): Runs the validation, making errors available in the form of a `validation` result object. This is invoked in a separate callback, as shown below:
```js
- asyncHandler(async (req, res, next) => {
+ async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -175,7 +175,7 @@ The functions are defined as below:
} else {
// Data from form is valid.
}
- });
+ };
```
We use the validation result's `isEmpty()` method to check if there were errors, and its `array()` method to get the set of error messages. See the [Handling validation section](https://express-validator.github.io/docs/guides/getting-started/#handling-validation-errors) for more information.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/update_book_form/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/update_book_form/index.md
index 578f38e451bcc97..3d9761c477beac2 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/update_book_form/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/forms/update_book_form/index.md
@@ -13,7 +13,7 @@ Open **/controllers/bookController.js**. Find the exported `book_update_get()` c
```js
// Display book update form on GET.
-exports.book_update_get = asyncHandler(async (req, res, next) => {
+exports.book_update_get = async (req, res, next) => {
// Get book, authors and genres for form.
const [book, allAuthors, allGenres] = await Promise.all([
Book.findById(req.params.id).populate("author").exec(),
@@ -39,7 +39,7 @@ exports.book_update_get = asyncHandler(async (req, res, next) => {
genres: allGenres,
book,
});
-});
+};
```
The controller gets the id of the `Book` to be updated from the URL parameter (`req.params.id`).
@@ -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()]() 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()`]() to the query.
We then mark the currently selected genres as checked and then render the **book_form.pug** view, passing variables for `title`, book, all `authors`, and all `genres`.
@@ -85,7 +85,7 @@ exports.book_update_post = [
body("genre.*").escape(),
// Process request after validation and sanitization.
- asyncHandler(async (req, res, next) => {
+ async (req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
@@ -123,11 +123,12 @@ exports.book_update_post = [
});
return;
}
+
// Data from form is valid. Update the record.
const updatedBook = await Book.findByIdAndUpdate(req.params.id, book, {});
// Redirect to book detail page.
res.redirect(updatedBook.url);
- }),
+ },
];
```
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/index.md
index e7da267fe99ee60..857fb2f9da131f0 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/index.md
@@ -9,10 +9,6 @@ sidebar: learnsidebar
Express is a popular unopinionated web framework, written in JavaScript and hosted within the Node.js runtime environment. This module explains some of the key benefits of the framework, how to set up your development environment and how to perform common web development and deployment tasks.
-> [!WARNING]
-> The Express documentation and tutorial are written for Express version 4, while the latest version is Express 5.
-> We plan to update the documentation in the second half of 2025.
-
## Prerequisites
Before starting this module you will need to understand what server-side web programming and web frameworks are, ideally by reading the topics in our [Server-side website programming first steps](/en-US/docs/Learn_web_development/Extensions/Server-side/First_steps) module. A general knowledge of programming concepts and [JavaScript](/en-US/docs/Web/JavaScript) is highly recommended, but not essential to understanding the core concepts.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/introduction/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/introduction/index.md
index 75778ad7c8d5aa8..d3ee94a1252e4a5 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/introduction/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/introduction/index.md
@@ -9,10 +9,6 @@ sidebar: learnsidebar
In this first Express article we answer the questions "What is Node?" and "What is Express?", and give you an overview of what makes the Express web framework special. We'll outline the main features, and show you some of the main building blocks of an Express application (although at this point you won't yet have a development environment in which to test it).
-> [!WARNING]
-> The Express tutorial is written for Express version 4, while the latest version is Express 5.
-> We plan to update the documentation in the second half of 2025.
-
@@ -167,9 +163,9 @@ app.listen(port, () => {
});
```
-The first two lines `require()` (import) the express module and create an [Express application](https://expressjs.com/en/4x/api.html#app). This object, which is traditionally named `app`, has methods for routing HTTP requests, configuring middleware, rendering HTML views, registering a template engine, and modifying [application settings](https://expressjs.com/en/4x/api.html#app.settings.table) that control how the application behaves (e.g., the environment mode, whether route definitions are case sensitive, etc.)
+The first two lines `require()` (import) the express module and create an [Express application](https://expressjs.com/en/5x/api.html#app). This object, which is traditionally named `app`, has methods for routing HTTP requests, configuring middleware, rendering HTML views, registering a template engine, and modifying [application settings](https://expressjs.com/en/5x/api.html#app.settings.table) that control how the application behaves (e.g., the environment mode, whether route definitions are case sensitive, etc.)
-The middle part of the code (the three lines starting with `app.get`) shows a _route definition_. The `app.get()` method specifies a callback function that will be invoked whenever there is an HTTP `GET` request with a path (`'/'`) relative to the site root. The callback function takes a request and a response object as arguments, and calls [`send()`](https://expressjs.com/en/4x/api.html#res.send) on the response to return the string "Hello World!"
+The middle part of the code (the three lines starting with `app.get`) shows a _route definition_. The `app.get()` method specifies a callback function that will be invoked whenever there is an HTTP `GET` request with a path (`'/'`) relative to the site root. The callback function takes a request and a response object as arguments, and calls [`send()`](https://expressjs.com/en/5x/api.html#res.send) on the response to return the string "Hello World!"
The final block starts up the server on a specified port ('3000') and prints a log comment to the console. With the server running, you could go to `localhost:3000` in your browser to see the example response returned.
@@ -177,7 +173,7 @@ The final block starts up the server on a specified port ('3000') and prints a l
A module is a JavaScript library/file that you can import into other code using Node's `require()` function. _Express_ itself is a module, as are the middleware and database libraries that we use in our _Express_ applications.
-The code below shows how we import a module by name, using the _Express_ framework as an example. First we invoke the `require()` function, specifying the name of the module as a string (`'express'`), and calling the returned object to create an [Express application](https://expressjs.com/en/4x/api.html#app). We can then access the properties and functions of the application object.
+The code below shows how we import a module by name, using the _Express_ framework as an example. First we invoke the `require()` function, specifying the name of the module as a string (`'express'`), and calling the returned object to create an [Express application](https://expressjs.com/en/5x/api.html#app). We can then access the properties and functions of the application object.
```js
const express = require("express");
@@ -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. 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.
-There are a number of ways for an asynchronous API to notify your application that it has completed. The most common way is to register a callback function when you invoke the asynchronous API, that will be called back when the operation completes. This is the approach used above.
+There are multiple ways for an asynchronous API to notify your application that it has completed. Historically, the approach used was to register a callback function when invoking the asynchronous API, which is then called when the operation completes (this is the approach used above).
> [!NOTE]
-> Using callbacks can be quite "messy" if you have a sequence of dependent asynchronous operations that must be performed in order because this results in multiple levels of nested callbacks. This problem is commonly known as "callback hell". This problem can be reduced by using modern JavaScript features like [Promises](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and [async/await](/en-US/docs/Web/JavaScript/Reference/Statements/async_function). Node offers the [`utils.promisify`](https://nodejs.org/api/util.html#utilpromisifyoriginal) function to do the callback → Promise conversion ergonomically.
+> Using callbacks can be quite "messy" if you have a sequence of dependent asynchronous operations that must be performed in order, because this results in multiple levels of nested callbacks. This problem is commonly known as "callback hell".
> [!NOTE]
> A common convention for Node and Express is to use error-first callbacks. In this convention, the first value in your _callback functions_ is an error value, while subsequent arguments contain success data. There is a good explanation of why this approach is useful in this blog: [The Node.js Way - Understanding Error-First Callbacks](https://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/) (fredkschott.com).
+Modern JavaScript code more commonly uses [Promises](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and [async/await](/en-US/docs/Web/JavaScript/Reference/Statements/async_function) to manage asynchronous program flow.
+You should use promises where possible. If working with code that uses callbacks, you can use the Node.js [`utils.promisify`](https://nodejs.org/api/util.html#utilpromisifyoriginal) function to handle the callback → Promise conversion ergonomically.
+
### Creating route handlers
In our _Hello World_ Express example (see above), we defined a (callback) route handler function for HTTP `GET` requests to the site root (`'/'`).
@@ -269,7 +268,7 @@ app.get("/", (req, res) => {
});
```
-The callback function takes a request and a response object as arguments. In this case, the method calls [`send()`](https://expressjs.com/en/4x/api.html#res.send) on the response to return the string "Hello World!" There are a [number of other response methods](https://expressjs.com/en/guide/routing.html#response-methods) for ending the request/response cycle, for example, you could call [`res.json()`](https://expressjs.com/en/4x/api.html#res.json) to send a JSON response or [`res.sendFile()`](https://expressjs.com/en/4x/api.html#res.sendFile) to send a file.
+The callback function takes a request and a response object as arguments. In this case, the method calls [`send()`](https://expressjs.com/en/5x/api.html#res.send) on the response to return the string "Hello World!" There are a [number of other response methods](https://expressjs.com/en/guide/routing.html#response-methods) for ending the request/response cycle, for example, you could call [`res.json()`](https://expressjs.com/en/5x/api.html#res.json) to send a JSON response or [`res.sendFile()`](https://expressjs.com/en/5x/api.html#res.sendFile) to send a file.
> [!NOTE]
> You can use any argument names you like in the callback functions; when the callback is invoked the first argument will always be the request and the second will always be the response. It makes sense to name them such that you can identify the object you're working with in the body of the callback.
@@ -391,7 +390,7 @@ The Express documentation has a lot more excellent documentation about [using](h
### Serving static files
-You can use the [express.static](https://expressjs.com/en/4x/api.html#express.static) middleware to serve static files, including your images, CSS and JavaScript (`static()` is the only middleware function that is actually **part** of _Express_). For example, you would use the line below to serve images, CSS files, and JavaScript files from a directory named '**public'** at the same level as where you call node:
+You can use the [express.static](https://expressjs.com/en/5x/api.html#express.static) middleware to serve static files, including your images, CSS and JavaScript (`static()` is the only middleware function that is actually **part** of _Express_). For example, you would use the line below to serve images, CSS files, and JavaScript files from a directory named '**public'** at the same level as where you call node:
```js
app.use(express.static("public"));
@@ -413,7 +412,7 @@ app.use(express.static("public"));
app.use(express.static("media"));
```
-You can also create a virtual prefix for your static URLs, rather than having the files added to the base URL. For example, here we [specify a mount path](https://expressjs.com/en/4x/api.html#app.use) so that the files are loaded with the prefix "/media":
+You can also create a virtual prefix for your static URLs, rather than having the files added to the base URL. For example, here we [specify a mount path](https://expressjs.com/en/5x/api.html#app.use) so that the files are loaded with the prefix "/media":
```js
app.use("/media", express.static("public"));
@@ -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.
-
-This works with older versions of MongoDB version ~ 2.2.33:
-
-```js
-const { MongoClient } = require("mongodb");
-
-MongoClient.connect("mongodb://localhost:27017/animals", (err, db) => {
- if (err) throw err;
-
- db.collection("mammals")
- .find()
- .toArray((err, result) => {
- if (err) throw err;
-
- console.log(result);
- });
-});
-```
-
-For MongoDB version 3.0 and up:
+The database itself can be installed locally or on a cloud server. In your Express code you import 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 (version 3.0 and up):
```js
const { MongoClient } = require("mongodb");
@@ -528,7 +508,7 @@ app.set("views", path.join(__dirname, "views"));
app.set("view engine", "some_template_engine_name");
```
-The appearance of the template will depend on what engine you use. Assuming that you have a template file named "index.\" that contains placeholders for data variables named 'title' and "message", you would call [`Response.render()`](https://expressjs.com/en/4x/api.html#res.render) in a route handler function to create and send the HTML response:
+The appearance of the template will depend on what engine you use. Assuming that you have a template file named "index.\" that contains placeholders for data variables named 'title' and "message", you would call [`Response.render()`](https://expressjs.com/en/5x/api.html#res.render) in a route handler function to create and send the HTML response:
```js
app.get("/", (req, res) => {
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/mongoose/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/mongoose/index.md
index fbbcc7dbd0fdde7..355e4bf8cc74435 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/mongoose/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/mongoose/index.md
@@ -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);`).
Replace the database URL string ('_insert_your_database_url_here_') with the location URL representing your own database (i.e., using the information from _MongoDB Atlas_).
```js
@@ -684,14 +684,24 @@ const mongoose = require("mongoose");
mongoose.set("strictQuery", false);
const mongoDB = "insert_your_database_url_here";
-main().catch((err) => console.log(err));
-async function main() {
+async function connectMongoose() {
await mongoose.connect(mongoDB);
}
+
+try {
+ connectMongoose();
+} catch (err) {
+ console.error("Failed to connect to MongoDB:", err);
+ process.exit(1);
+}
```
As discussed in the [Mongoose primer](#connecting_to_mongodb) above, this code creates the default connection to the database and reports any errors to the console.
+> [!NOTE]
+> We could have put the database connection code in our **app.js** code.
+> Putting it in the application entry point decouples the application and database, which makes it easier to use a different database for running test code.
+
Note that hard-coding database credentials in source code as shown above is not recommended.
We do it here because it shows the core connection code, and because during development there is no significant risk that leaking these details will expose or corrupt sensitive information.
We'll show you how to do this more safely when [deploying to production](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/deployment#database_configuration)!
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/routes/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/routes/index.md
index b5581027c070842..c1582781c59c944 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/routes/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/routes/index.md
@@ -45,7 +45,7 @@ As we've already created the models, the main things we'll need to create are:
Ultimately we might have pages to show lists and detail information for books, genres, authors and bookinstances, along with pages to create, update, and delete records. That's a lot to document in one article. Therefore most of this article will concentrate on setting up our routes and controllers to return "dummy" content. We'll extend the controller methods in our subsequent articles to work with model data.
-The first section below provides a brief "primer" on how to use the Express [Router](https://expressjs.com/en/4x/api.html#router) middleware. We'll then use that knowledge in the following sections when we set up the LocalLibrary routes.
+The first section below provides a brief "primer" on how to use the Express [Router](https://expressjs.com/en/5x/api.html#router) middleware. We'll then use that knowledge in the following sections when we set up the LocalLibrary routes.
## Routes primer
@@ -115,7 +115,7 @@ The callback takes three arguments (usually named as shown: `req`, `res`, `next`
>
> The router function above takes a single callback, but you can specify as many callback arguments as you want, or an array of callback functions. Each function is part of the middleware chain, and will be called in the order it is added to the chain (unless a preceding function completes the request).
-The callback function here calls [`send()`](https://expressjs.com/en/4x/api.html#res.send) on the response to return the string "About this wiki" when we receive a GET request with the path (`/about`). There are a [number of other response methods](https://expressjs.com/en/guide/routing.html#response-methods) for ending the request/response cycle. For example, you could call [`res.json()`](https://expressjs.com/en/4x/api.html#res.json) to send a JSON response or [`res.sendFile()`](https://expressjs.com/en/4x/api.html#res.sendFile) to send a file. The response method that we'll be using most often as we build up the library is [`render()`](https://expressjs.com/en/4x/api.html#res.render), which creates and returns HTML files using templates and data—we'll talk a lot more about that in a later article!
+The callback function here calls [`send()`](https://expressjs.com/en/5x/api.html#res.send) on the response to return the string "About this wiki" when we receive a GET request with the path (`/about`). There are a [number of other response methods](https://expressjs.com/en/guide/routing.html#response-methods) for ending the request/response cycle. For example, you could call [`res.json()`](https://expressjs.com/en/5x/api.html#res.json) to send a JSON response or [`res.sendFile()`](https://expressjs.com/en/5x/api.html#res.sendFile) to send a file. The response method that we'll be using most often as we build up the library is [`render()`](https://expressjs.com/en/5x/api.html#res.render), which creates and returns HTML files using templates and data—we'll talk a lot more about that in a later article!
### HTTP verbs
@@ -135,108 +135,159 @@ router.post("/about", (req, res) => {
The route paths define the endpoints at which requests can be made. The examples we've seen so far have just been strings, and are used exactly as written: '/', '/about', '/book', '/any-random.path'.
-Route paths can also be string patterns. String patterns use a form of regular expression syntax to define _patterns_ of endpoints that will be matched. The syntax is listed below (note that the hyphen (`-`) and the dot (`.`) are interpreted literally by string-based paths):
+Route paths can also be string patterns. String patterns use a form of regular expression syntax to define _patterns_ of endpoints that will be matched.
+Most of our routes for the LocalLibrary will use strings and not regular expressions
+We'll also use route parameters as discussed in the next section.
-- `?` : The endpoint must have 0 or 1 of the preceding character (or group), e.g., a route path of `'/ab?cd'` will match endpoints `acd` or `abcd`.
-- `+` : The endpoint must have 1 or more of the preceding character (or group), e.g., a route path of `'/ab+cd'` will match endpoints `abcd`, `abbcd`, `abbbcd`, and so on.
-- `*` : The endpoint may have an arbitrary string where the `*` character is placed. E.g. a route path of `'/ab*cd'` will match endpoints `abcd`, `abXcd`, `abSOMErandomTEXTcd`, and so on.
-- `()` : Grouping match on a set of characters to perform another operation on, e.g., `'/ab(cd)?e'` will perform a `?`-match on the group `(cd)` — it will match `abe` and `abcde`.
+### Route parameters
+
+Route parameters are _named URL segments_ used to capture values at specific positions in the URL. The named segments are prefixed with a colon and then the name (E.g., `/:your_parameter_name/`). The captured values are stored in the `req.params` object using the parameter names as keys (E.g., `req.params.your_parameter_name`).
-The route paths can also be JavaScript [regular expressions](/en-US/docs/Web/JavaScript/Guide/Regular_expressions). For example, the route path below will match `catfish` and `dogfish`, but not `catflap`, `catfishhead`, and so on. Note that the path for a regular expression uses regular expression syntax (it is not a quoted string as in the previous cases).
+So for example, consider a URL encoded to contain information about users and books: `http://localhost:3000/users/34/books/8989`. We can extract this information as shown below, with the `userId` and `bookId` path parameters:
```js
-app.get(/.*fish$/, (req, res) => {
- // …
+app.get("/users/:userId/books/:bookId", (req, res) => {
+ // Access userId via: req.params.userId
+ // Access bookId via: req.params.bookId
+ res.send(req.params);
});
```
> [!NOTE]
-> Most of our routes for the LocalLibrary will use strings and not regular expressions. We'll also use route parameters as discussed in the next section.
+> The URL _/book/create_ will be matched by a route like `/book/:bookId` (because `:bookId` is a placeholder for _any_ string, therefore `create` matches). The first route that matches an incoming URL will be used, so if you want to process `/book/create` URLs specifically, their route handler must be defined before your `/book/:bookId` route.
-### Route parameters
+Route parameter names (for example, `bookId`, above) can be any valid JavaScript identifier that starts with a letter, `_`, or `$`. You can include digits after the first character, but not hyphens and spaces.
+You can also use names that aren't valid JavaScript identifiers, including spaces, hyphens, emoticons, or any other character, but you need to define them with a quoted string and access them using bracket notation.
+For example:
-Route parameters are _named URL segments_ used to capture values at specific positions in the URL. The named segments are prefixed with a colon and then the name (E.g., `/:your_parameter_name/`). The captured values are stored in the `req.params` object using the parameter names as keys (E.g., `req.params.your_parameter_name`).
+```js
+app.get('/users/:"user id"/books/:"book-id"', (req, res) => {
+ // Access quoted param using bracket notation
+ const user = req.params["user id"];
+ const book = req.params["book-id"];
+ res.send({ user, book });
+});
+```
-So for example, consider a URL encoded to contain information about users and books: `http://localhost:3000/users/34/books/8989`. We can extract this information as shown below, with the `userId` and `bookId` path parameters:
+### Wildcards
+
+Wildcard parameters match one or more characters across multiple segments, returning each segment as a value in an array.
+They are defined the same way as regular parameters, but are prefixed with an asterisk.
+
+So for example, consider the URL `http://localhost:3000/users/34/books/8989`, we can extract all the information after `users/` with the `example` wildcard:
```js
-app.get("/users/:userId/books/:bookId", (req, res) => {
- // Access userId via: req.params.userId
- // Access bookId via: req.params.bookId
+app.get("/users/*example", (req, res) => {
+ // req.params would contain { "example": ["34", "books", "8989"]}
res.send(req.params);
});
```
-The names of route parameters must be made up of "word characters" (A-Z, a-z, 0-9, and \_).
+### Optional parts
-> [!NOTE]
-> The URL _/book/create_ will be matched by a route like `/book/:bookId` (because `:bookId` is a placeholder for _any_ string, therefore `create` matches). The first route that matches an incoming URL will be used, so if you want to process `/book/create` URLs specifically, their route handler must be defined before your `/book/:bookId` route.
+Braces can be used to define parts of the path that are optional.
+For example, below we match a filename with any extension (or none).
+
+```js
+app.get("/file/:filename{.:ext}", (req, res) => {
+ // Given URL: http://localhost:3000/file/somefile.md`
+ // req.params would contain { "filename": "somefile", "ext": "md"}
+ res.send(req.params);
+});
+```
+
+### Reserved characters
+
+The following characters are reserved:`(()[]?+!)`.
+If you want to use them, you must escape them with a backslash (`\`).
-That's all you need to get started with routes - if needed you can find more information in the Express docs: [Basic routing](https://expressjs.com/en/starter/basic-routing.html) and [Routing guide](https://expressjs.com/en/guide/routing.html). The following sections show how we'll set up our routes and controllers for the LocalLibrary.
+You also can't use the pipe character (`|`) in a regular expression.
-### Handling errors in the route functions
+That's all you need to get started with routes.
+If needed, you can find more information in the Express docs: [Basic routing](https://expressjs.com/en/starter/basic-routing.html) and [Routing guide](https://expressjs.com/en/guide/routing.html). The following sections show how we'll set up our routes and controllers for the LocalLibrary.
+
+### Handling errors and exceptions in the route functions
The route functions shown earlier all have arguments `req` and `res`, which represent the request and response, respectively.
-Route functions are also called with a third argument `next`, which can be used to pass errors to the Express middleware chain.
+Route functions are also passed a third argument, `next`, which contains a callback function that can be called to pass any errors or exceptions to the Express middleware chain, where they will eventually propagate to your global error handling code.
+
+From Express 5, `next` is called automatically with the rejection value if a route handler returns a [Promise](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that subsequently rejects; therefore, no error handling code is required in route functions when using promises.
+This leads to very compact code when working with asynchronous promise-based APIs, in particular when using [`async` and `await`](/en-US/docs/Learn_web_development/Extensions/Async_JS/Promises#async_and_await).
-The code below shows how this works, using the example of a database query that takes a callback function, and returns either an error `err` or some results.
-If `err` is returned, `next` is called with `err` as the value in its first parameter (eventually the error propagates to our global error handling code).
-On success the desired data is returned and then used in the response.
+For example, the following code uses the `find()` method to query a database and then renders the result.
```js
-router.get("/about", (req, res, next) => {
- About.find({}).exec((err, queryResults) => {
- if (err) {
- return next(err);
- }
- // Successful, so render
- res.render("about_view", { title: "About", list: queryResults });
- });
+exports.get("/about", async (req, res, next) => {
+ const successfulResult = await About.find({}).exec();
+ res.render("about_view", { title: "About", list: successfulResult });
});
```
-### Handling exceptions in route functions
-
-The previous section shows how Express expects route functions to return errors.
-The framework is designed for use with asynchronous functions that take a callback function (with an error and result argument), which is called when the operation completes.
-That's a problem because later on we will be making Mongoose database queries that use [Promise](/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)-based APIs, and which may throw exceptions in our route functions (rather than returning errors in a callback).
+The code below shows the same example using a promise chain.
+Note that if you wanted to, you could `catch()` the error and implement your own custom handling.
-In order for the framework to properly handle exceptions, they must be caught, and then forwarded as errors as shown in the previous section.
+```js
+exports.get("/about", (req, res, next) => {
+ // Removed 'async'
+ return About.find({})
+ .exec()
+ .then((successfulResult) => {
+ res.render("about_view", { title: "About", list: successfulResult });
+ });
+ /*
+ .catch(err => {
+ next(err);
+ });
+ */
+});
+```
> [!NOTE]
-> Express 5, which is currently in beta, is expected to handle JavaScript exceptions natively.
+> Most modern APIs are asynchronous and promise-based, so error handling is often that straightforward.
+> Certainly that's all you really _need_ to know about error handling for this tutorial!
-Re-imagining the simple example from the previous section with `About.find().exec()` as a database query that returns a promise, we might write the route function inside a [`try...catch`](/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) block like this:
+Express 5 automatically catches and forwards exceptions that are thrown in synchronous code:
```js
-exports.get("/about", async (req, res, next) => {
- try {
- const successfulResult = await About.find({}).exec();
- res.render("about_view", { title: "About", list: successfulResult });
- } catch (error) {
- return next(error);
- }
+app.get("/", (req, res) => {
+ // Express will catch this
+ throw new Error("SynchronousException");
});
```
-That's quite a lot of boilerplate code to add to every function.
-Instead, for this tutorial we'll use the [express-async-handler](https://www.npmjs.com/package/express-async-handler) module.
-This defines a wrapper function that hides the `try...catch` block and the code to forward the error.
-The same example is now very simple, because we only need to write code for the case where we assume success:
+However, you must [`catch()`](/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) exceptions occurring in asynchronous code invoked by route handlers or middleware. These will not be caught by the default code:
```js
-// Import the module
-const asyncHandler = require("express-async-handler");
-
-exports.get(
- "/about",
- asyncHandler(async (req, res, next) => {
- const successfulResult = await About.find({}).exec();
- res.render("about_view", { title: "About", list: successfulResult });
- }),
-);
+app.get("/", (req, res, next) => {
+ setTimeout(() => {
+ try {
+ // You must catch and propagate this error yourself
+ throw new Error("AsynchronousException");
+ } catch (err) {
+ next(err);
+ }
+ }, 100);
+});
```
+Lastly, if you're using the older style of asynchronous methods that return an error or result in a callback function, then you need to propagate the error yourself.
+The following example shows how.
+
+```js
+router.get("/about", (req, res, next) => {
+ About.find({}).exec((err, queryResults) => {
+ if (err) {
+ // Propagate the error
+ return next(err);
+ }
+ // Successful, so render
+ res.render("about_view", { title: "About", list: queryResults });
+ });
+});
+```
+
+For more information see [Error handling](https://expressjs.com/en/guide/error-handling.html).
+
## Routes needed for the LocalLibrary
The URLs that we're ultimately going to need for our pages are listed below, where _object_ is replaced by the name of each of our models (book, bookinstance, genre, author), _objects_ is the plural of object, and _id_ is the unique instance field (`_id`) that is given to each Mongoose model instance by default.
@@ -272,119 +323,107 @@ Start by creating a folder for our controllers in the project root (**/controlle
genreController.js
```
-The controllers will use the `express-async-handler` module, so before we proceed, install it into the library using `npm`:
-
-```bash
-npm install express-async-handler
-```
-
### Author controller
Open the **/controllers/authorController.js** file and type in the following code:
```js
const Author = require("../models/author");
-const asyncHandler = require("express-async-handler");
// Display list of all Authors.
-exports.author_list = asyncHandler(async (req, res, next) => {
+exports.author_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author list");
-});
+};
// Display detail page for a specific Author.
-exports.author_detail = asyncHandler(async (req, res, next) => {
+exports.author_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: Author detail: ${req.params.id}`);
-});
+};
// Display Author create form on GET.
-exports.author_create_get = asyncHandler(async (req, res, next) => {
+exports.author_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author create GET");
-});
+};
// Handle Author create on POST.
-exports.author_create_post = asyncHandler(async (req, res, next) => {
+exports.author_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author create POST");
-});
+};
// Display Author delete form on GET.
-exports.author_delete_get = asyncHandler(async (req, res, next) => {
+exports.author_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author delete GET");
-});
+};
// Handle Author delete on POST.
-exports.author_delete_post = asyncHandler(async (req, res, next) => {
+exports.author_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author delete POST");
-});
+};
// Display Author update form on GET.
-exports.author_update_get = asyncHandler(async (req, res, next) => {
+exports.author_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author update GET");
-});
+};
// Handle Author update on POST.
-exports.author_update_post = asyncHandler(async (req, res, next) => {
+exports.author_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author update POST");
-});
+};
```
-The module first requires the `Author` model that we'll later be using to access and update our data, and the `asyncHandler` wrapper we'll use to catch any exceptions thrown in our route handler functions.
+The module first requires the `Author` model that we'll later be using to access and update our data.
It then exports functions for each of the URLs we wish to handle.
Note that the create, update and delete operations use forms, and hence also have additional methods for handling form post requests — we'll discuss those methods in the "forms article" later on.
-The functions all use the wrapper function described above in [Handling exceptions in route functions](#handling_exceptions_in_route_functions), with arguments for the request, response, and next.
The functions respond with a string indicating that the associated page has not yet been created.
If a controller function is expected to receive path parameters, these are output in the message string (see `req.params.id` above).
-Note that once implemented, some route functions might not contain any code that can throw exceptions.
-We can change those back to "normal" route handler functions when we get to them.
-
#### BookInstance controller
Open the **/controllers/bookinstanceController.js** file and copy in the following code (this follows an identical pattern to the `Author` controller module):
```js
const BookInstance = require("../models/bookinstance");
-const asyncHandler = require("express-async-handler");
// Display list of all BookInstances.
-exports.bookinstance_list = asyncHandler(async (req, res, next) => {
+exports.bookinstance_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance list");
-});
+};
// Display detail page for a specific BookInstance.
-exports.bookinstance_detail = asyncHandler(async (req, res, next) => {
+exports.bookinstance_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: BookInstance detail: ${req.params.id}`);
-});
+};
// Display BookInstance create form on GET.
-exports.bookinstance_create_get = asyncHandler(async (req, res, next) => {
+exports.bookinstance_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance create GET");
-});
+};
// Handle BookInstance create on POST.
-exports.bookinstance_create_post = asyncHandler(async (req, res, next) => {
+exports.bookinstance_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance create POST");
-});
+};
// Display BookInstance delete form on GET.
-exports.bookinstance_delete_get = asyncHandler(async (req, res, next) => {
+exports.bookinstance_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance delete GET");
-});
+};
// Handle BookInstance delete on POST.
-exports.bookinstance_delete_post = asyncHandler(async (req, res, next) => {
+exports.bookinstance_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance delete POST");
-});
+};
// Display BookInstance update form on GET.
-exports.bookinstance_update_get = asyncHandler(async (req, res, next) => {
+exports.bookinstance_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance update GET");
-});
+};
// Handle bookinstance update on POST.
-exports.bookinstance_update_post = asyncHandler(async (req, res, next) => {
+exports.bookinstance_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance update POST");
-});
+};
```
#### Genre controller
@@ -393,47 +432,46 @@ Open the **/controllers/genreController.js** file and copy in the following text
```js
const Genre = require("../models/genre");
-const asyncHandler = require("express-async-handler");
// Display list of all Genre.
-exports.genre_list = asyncHandler(async (req, res, next) => {
+exports.genre_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre list");
-});
+};
// Display detail page for a specific Genre.
-exports.genre_detail = asyncHandler(async (req, res, next) => {
+exports.genre_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: Genre detail: ${req.params.id}`);
-});
+};
// Display Genre create form on GET.
-exports.genre_create_get = asyncHandler(async (req, res, next) => {
+exports.genre_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre create GET");
-});
+};
// Handle Genre create on POST.
-exports.genre_create_post = asyncHandler(async (req, res, next) => {
+exports.genre_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre create POST");
-});
+};
// Display Genre delete form on GET.
-exports.genre_delete_get = asyncHandler(async (req, res, next) => {
+exports.genre_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre delete GET");
-});
+};
// Handle Genre delete on POST.
-exports.genre_delete_post = asyncHandler(async (req, res, next) => {
+exports.genre_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre delete POST");
-});
+};
// Display Genre update form on GET.
-exports.genre_update_get = asyncHandler(async (req, res, next) => {
+exports.genre_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre update GET");
-});
+};
// Handle Genre update on POST.
-exports.genre_update_post = asyncHandler(async (req, res, next) => {
+exports.genre_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre update POST");
-});
+};
```
#### Book controller
@@ -443,51 +481,50 @@ This follows the same pattern as the other controller modules, but additionally
```js
const Book = require("../models/book");
-const asyncHandler = require("express-async-handler");
-exports.index = asyncHandler(async (req, res, next) => {
+exports.index = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Site Home Page");
-});
+};
// Display list of all books.
-exports.book_list = asyncHandler(async (req, res, next) => {
+exports.book_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book list");
-});
+};
// Display detail page for a specific book.
-exports.book_detail = asyncHandler(async (req, res, next) => {
+exports.book_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: Book detail: ${req.params.id}`);
-});
+};
// Display book create form on GET.
-exports.book_create_get = asyncHandler(async (req, res, next) => {
+exports.book_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book create GET");
-});
+};
// Handle book create on POST.
-exports.book_create_post = asyncHandler(async (req, res, next) => {
+exports.book_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book create POST");
-});
+};
// Display book delete form on GET.
-exports.book_delete_get = asyncHandler(async (req, res, next) => {
+exports.book_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book delete GET");
-});
+};
// Handle book delete on POST.
-exports.book_delete_post = asyncHandler(async (req, res, next) => {
+exports.book_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book delete POST");
-});
+};
// Display book update form on GET.
-exports.book_update_get = asyncHandler(async (req, res, next) => {
+exports.book_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book update GET");
-});
+};
// Handle book update on POST.
-exports.book_update_post = asyncHandler(async (req, res, next) => {
+exports.book_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book update POST");
-});
+};
```
## Create the catalog route module
@@ -655,7 +692,7 @@ The handler functions are all imported from the controller modules we created in
### Update the index route module
-We've set up all our new routes, but we still have a route to the original page. Let's instead redirect this to the new index page that we've created at the path '/catalog'.
+We've set up all our new routes, but we still have a route to the original page. Let's instead redirect this to the new index page that we've created at the path `/catalog`.
Open **/routes/index.js** and replace the existing route with the function below.
@@ -667,7 +704,7 @@ router.get("/", (req, res) => {
```
> [!NOTE]
-> This is our first use of the [redirect()](https://expressjs.com/en/4x/api.html#res.redirect) response method. This redirects to the specified page, by default sending HTTP status code "302 Found". You can change the status code returned if needed, and supply either absolute or relative paths.
+> This is our first use of the [redirect()](https://expressjs.com/en/5x/api.html#res.redirect) response method. This redirects to the specified page, by default sending HTTP status code "302 Found". You can change the status code returned if needed, and supply either absolute or relative paths.
### Update app.js
@@ -691,7 +728,7 @@ app.use("/catalog", catalogRouter); // Add catalog routes to middleware chain.
```
> [!NOTE]
-> We have added our catalog module at a path `'/catalog'`. This is prepended to all of the paths defined in the catalog module. So for example, to access a list of books, the URL will be: `/catalog/books/`.
+> We have added our catalog module at a path `/catalog`. This is prepended to all of the paths defined in the catalog module. So for example, to access a list of books, the URL will be: `/catalog/books/`.
That's it. We should now have routes and skeleton functions enabled for all the URLs that we will eventually support on the LocalLibrary website.
diff --git a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/skeleton_website/index.md b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/skeleton_website/index.md
index eaa80f8dd9d1cd3..09ff653eb87deab 100644
--- a/files/en-us/learn_web_development/extensions/server-side/express_nodejs/skeleton_website/index.md
+++ b/files/en-us/learn_web_development/extensions/server-side/express_nodejs/skeleton_website/index.md
@@ -10,10 +10,6 @@ sidebar: learnsidebar
This second article in our [Express Tutorial](/en-US/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Tutorial_local_library_website) shows how you can create a "skeleton" website project which you can then go on to populate with site-specific routes, templates/views, and database calls.
-> [!WARNING]
-> The Express tutorial is written for Express version 4, while the latest version is Express 5.
-> We plan to update the documentation in the second half of 2025.
-
@@ -44,7 +40,7 @@ The following sections show you how to call the application generator, and provi
> - The _Express Application Generator_ declares most variables using `var`.
> We have changed most of these to [`const`](/en-US/docs/Web/JavaScript/Reference/Statements/const) (and a few to [`let`](/en-US/docs/Web/JavaScript/Reference/Statements/let)) in the tutorial, because we want to demonstrate modern JavaScript practice.
> - This tutorial uses the version of _Express_ and other dependencies that are defined in the **package.json** created by the _Express Application Generator_.
-> These are not (necessarily) the latest version, and you may want to update them when deploying a real application to production.
+> These are not (necessarily) the latest version, and you should update them when deploying a real application to production.
## Using the application generator
@@ -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 (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.
### What CSS stylesheet engine should I use?
@@ -170,7 +166,7 @@ The generator will create (and list) the project's files.
> DEBUG=express-locallibrary-tutorial:* npm start
run the app (PowerShell (Windows))
- > $ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start
+ > $env:DEBUG = "express-locallibrary-tutorial:*"; npm start
run the app (Command Prompt (Windows)):
> SET DEBUG=express-locallibrary-tutorial:* & npm start
@@ -203,7 +199,7 @@ At this point, we have a complete skeleton project. The website doesn't actually
- On Windows PowerShell, use this command:
```powershell
- ENV:DEBUG = "express-locallibrary-tutorial:*"; npm start
+ $env:DEBUG = "express-locallibrary-tutorial:*"; npm start
```
> [!NOTE]
@@ -259,11 +255,11 @@ If you open your project's **package.json** file you'll now see a new section wi
```json
"devDependencies": {
- "nodemon": "^3.1.3"
+ "nodemon": "^3.1.10"
}
```
-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:
- On Linux and macOS, the scripts section will look like this:
@@ -293,6 +289,7 @@ We can now start the server in almost exactly the same way as previously, but us
## The generated project
Let's now take a look at the project we just created.
+We'll be making some minor modifications to this as we go along.
### Directory structure
@@ -349,7 +346,7 @@ The **package.json** file defines the application dependencies and other informa
"pug": "2.0.0-beta11"
},
"devDependencies": {
- "nodemon": "^3.1.3"
+ "nodemon": "^3.1.10"
}
}
```
@@ -380,9 +377,9 @@ Replace the dependencies section of your `package.json` file with the following
```json
"dependencies": {
- "cookie-parser": "^1.4.6",
- "debug": "^4.3.5",
- "express": "^4.19.2",
+ "cookie-parser": "^1.4.7",
+ "debug": "^4.4.1",
+ "express": "^5.1.0",
"http-errors": "~2.0.0",
"morgan": "^1.10.0",
"pug": "3.0.3"
@@ -553,7 +550,7 @@ One thing of interest above is that the callback function has the third argument
### Views (templates)
-The views (templates) are stored in the **/views** directory (as specified in **app.js**) and are given the file extension **.pug**. The method [`Response.render()`](https://expressjs.com/en/4x/api.html#res.render) is used to render a specified template along with the values of named variables passed in an object, and then send the result as a response. In the code below from **/routes/index.js** you can see how that route renders a response using the template "index" passing the template variable "title".
+The views (templates) are stored in the **/views** directory (as specified in **app.js**) and are given the file extension **.pug**. The method [`Response.render()`](https://expressjs.com/en/5x/api.html#res.render) is used to render a specified template along with the values of named variables passed in an object, and then send the result as a response. In the code below from **/routes/index.js** you can see how that route renders a response using the template "index" passing the template variable "title".
```js
/* GET home page. */