From b1820419d59ffe3ca3570be70a230c29549b9b6d Mon Sep 17 00:00:00 2001 From: ARYAN0-WORK Date: Sun, 24 May 2026 10:22:06 +0000 Subject: [PATCH 1/5] docs(5x): improving wording in error handling guide --- src/content/docs/en/5x/guide/error-handling.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/docs/en/5x/guide/error-handling.mdx b/src/content/docs/en/5x/guide/error-handling.mdx index f933d9e32b..69dad2499e 100644 --- a/src/content/docs/en/5x/guide/error-handling.mdx +++ b/src/content/docs/en/5x/guide/error-handling.mdx @@ -180,7 +180,7 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -Since promises automatically catch both synchronous errors and rejected promises, +Since promises automatically handle both synchronous errors and rejected promises, you can simply provide `next` as the final catch handler and Express will catch errors, because the catch handler is given the error as the first argument. @@ -219,7 +219,7 @@ app.get('/', [ ]); ``` -The above example has a couple of trivial statements from the `readFile` +The above example contains a couple of trivial statements following the `readFile` call. If `readFile` causes an error, then it passes the error to Express, otherwise you quickly return to the world of synchronous error handling in the next handler in the chain. Then, the example above tries to process the data. If this fails, then the @@ -424,7 +424,7 @@ function logErrors(err: Error, req: Request, res: Response, next: NextFunction) Also in this example, `clientErrorHandler` is defined as follows; in this case, the error is explicitly passed along to the next one. -Notice that when _not_ calling "next" in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will "hang" and will not be eligible for garbage collection. +Notice that when you do not call "next" in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will "hang" and will not be eligible for garbage collection. ```js function clientErrorHandler(err, req, res, next) { From efdf005ce5ae46b8d25fd1d8ec9ff27f7a84e76e Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 2 Aug 2026 15:20:07 -0500 Subject: [PATCH 2/5] docs: apply error-handling wording fixes to 4.x and refine promise phrasing --- src/content/docs/en/4x/guide/error-handling.mdx | 8 +++----- src/content/docs/en/5x/guide/error-handling.mdx | 6 ++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/content/docs/en/4x/guide/error-handling.mdx b/src/content/docs/en/4x/guide/error-handling.mdx index 7cafabc5f6..44bb8b25b0 100644 --- a/src/content/docs/en/4x/guide/error-handling.mdx +++ b/src/content/docs/en/4x/guide/error-handling.mdx @@ -180,9 +180,7 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -Since promises automatically catch both synchronous errors and rejected promises, -you can simply provide `next` as the final catch handler and Express will catch errors, -because the catch handler is given the error as the first argument. +If a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final `.catch`. There, passing `next` as the handler is enough: `.catch` calls it with the error as its first argument, which is exactly the argument `next` expects, so the error reaches Express. You could also use a chain of handlers to rely on synchronous error catching, by reducing the asynchronous code to something trivial. For example: @@ -219,7 +217,7 @@ app.get('/', [ ]); ``` -The above example has a couple of trivial statements from the `readFile` +The above example contains a couple of trivial statements following the `readFile` call. If `readFile` causes an error, then it passes the error to Express, otherwise you quickly return to the world of synchronous error handling in the next handler in the chain. Then, the example above tries to process the data. If this fails, then the @@ -424,7 +422,7 @@ function logErrors(err: Error, req: Request, res: Response, next: NextFunction) Also in this example, `clientErrorHandler` is defined as follows; in this case, the error is explicitly passed along to the next one. -Notice that when _not_ calling "next" in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will "hang" and will not be eligible for garbage collection. +Notice that when you do not call `next` in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will "hang" and will not be eligible for garbage collection. ```js function clientErrorHandler(err, req, res, next) { diff --git a/src/content/docs/en/5x/guide/error-handling.mdx b/src/content/docs/en/5x/guide/error-handling.mdx index 69dad2499e..ff43eb9e9e 100644 --- a/src/content/docs/en/5x/guide/error-handling.mdx +++ b/src/content/docs/en/5x/guide/error-handling.mdx @@ -180,9 +180,7 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -Since promises automatically handle both synchronous errors and rejected promises, -you can simply provide `next` as the final catch handler and Express will catch errors, -because the catch handler is given the error as the first argument. +If a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final `.catch`. There, passing `next` as the handler is enough: `.catch` calls it with the error as its first argument, which is exactly the argument `next` expects, so the error reaches Express. You could also use a chain of handlers to rely on synchronous error catching, by reducing the asynchronous code to something trivial. For example: @@ -424,7 +422,7 @@ function logErrors(err: Error, req: Request, res: Response, next: NextFunction) Also in this example, `clientErrorHandler` is defined as follows; in this case, the error is explicitly passed along to the next one. -Notice that when you do not call "next" in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will "hang" and will not be eligible for garbage collection. +Notice that when you do not call `next` in an error-handling function, you are responsible for writing (and ending) the response. Otherwise, those requests will "hang" and will not be eligible for garbage collection. ```js function clientErrorHandler(err, req, res, next) { From ae0b5c6428ee01eb6f9cdc5c03ed74397bf85b30 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 2 Aug 2026 15:29:14 -0500 Subject: [PATCH 3/5] docs(5x): clarify error handling for promises in route handlers and middleware --- .../docs/en/4x/guide/error-handling.mdx | 30 ++++++++++++------- .../docs/en/5x/guide/error-handling.mdx | 16 ++++++++-- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/content/docs/en/4x/guide/error-handling.mdx b/src/content/docs/en/4x/guide/error-handling.mdx index 44bb8b25b0..a539ab4d01 100644 --- a/src/content/docs/en/4x/guide/error-handling.mdx +++ b/src/content/docs/en/4x/guide/error-handling.mdx @@ -62,14 +62,16 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -Starting with Express 5, route handlers and middleware that return a Promise -will call `next(value)` automatically when they reject or throw an error. -For example: +Errors from rejected promises are not passed to `next` automatically, and this includes `async` functions: if an `async` route handler throws or awaits a rejected promise, the request will hang. You must catch the error yourself and pass it to Express: ```js app.get('/user/:id', async (req, res, next) => { - const user = await getUserById(req.params.id); - res.send(user); + try { + const user = await getUserById(req.params.id); + res.send(user); + } catch (err) { + next(err); + } }); ``` @@ -77,14 +79,22 @@ app.get('/user/:id', async (req, res, next) => { import { type Request, type Response, type NextFunction } from 'express'; app.get('/user/:id', async (req: Request, res: Response, next: NextFunction) => { - const user = await getUserById(req.params.id); - res.send(user); + try { + const user = await getUserById(req.params.id); + res.send(user); + } catch (err) { + next(err); + } }); ``` -If `getUserById` throws an error or rejects, `next` will be called with either -the thrown error or the rejected value. If no rejected value is provided, `next` -will be called with a default Error object provided by the Express router. + + +Consider [updating to Express 5](/guide/migrating-5), where route handlers and middleware that +return a Promise call `next(value)` automatically when they reject or throw an error, making the +`try...catch` above unnecessary. + + If you pass anything to the `next()` function (except the string `'route'`), Express regards the current request as being an error and will skip any diff --git a/src/content/docs/en/5x/guide/error-handling.mdx b/src/content/docs/en/5x/guide/error-handling.mdx index ff43eb9e9e..c8184815f3 100644 --- a/src/content/docs/en/5x/guide/error-handling.mdx +++ b/src/content/docs/en/5x/guide/error-handling.mdx @@ -62,9 +62,9 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -Starting with Express 5, route handlers and middleware that return a Promise -will call `next(value)` automatically when they reject or throw an error. -For example: +Route handlers and middleware that return a Promise call `next(value)` +automatically when they reject or throw an error. This includes `async` +functions, which always return a Promise. For example: ```js app.get('/user/:id', async (req, res, next) => { @@ -182,6 +182,16 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { If a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final `.catch`. There, passing `next` as the handler is enough: `.catch` calls it with the error as its first argument, which is exactly the argument `next` expects, so the error reaches Express. +Note that the handler above does not return the promise chain, so Express does not know it exists, and attaching `.catch(next)` is what routes the error. Without the `.catch(next)`, the rejection would be unhandled and crash the process. If you return the promise instead, Express watches it and calls `next` automatically when it rejects, so the `.catch` is no longer needed: + +```js +app.get('/', (req, res) => { + return Promise.resolve().then(() => { + throw new Error('BROKEN'); // Express will catch this and call next. + }); +}); +``` + You could also use a chain of handlers to rely on synchronous error catching, by reducing the asynchronous code to something trivial. For example: From 698232f8103a59b0019d44ef01ca159cca73df94 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 2 Aug 2026 15:33:05 -0500 Subject: [PATCH 4/5] docs: clarify error handling for rejected promises in async functions --- src/content/docs/en/4x/guide/error-handling.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/en/4x/guide/error-handling.mdx b/src/content/docs/en/4x/guide/error-handling.mdx index a539ab4d01..de6641684c 100644 --- a/src/content/docs/en/4x/guide/error-handling.mdx +++ b/src/content/docs/en/4x/guide/error-handling.mdx @@ -62,7 +62,7 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -Errors from rejected promises are not passed to `next` automatically, and this includes `async` functions: if an `async` route handler throws or awaits a rejected promise, the request will hang. You must catch the error yourself and pass it to Express: +Errors from rejected promises are not passed to `next` automatically, and this includes `async` functions: if an `async` route handler throws or awaits a rejected promise, the rejection is unhandled, which crashes the process on current Node.js versions. You must catch the error yourself and pass it to Express: ```js app.get('/user/:id', async (req, res, next) => { From fba08dae2ad68f79f1a9fc37feb069a9cb0d1e5a Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 2 Aug 2026 15:38:02 -0500 Subject: [PATCH 5/5] docs: enhance error handling section with clearer explanations for synchronous and asynchronous errors --- .../docs/en/5x/guide/error-handling.mdx | 214 ++++++++++-------- 1 file changed, 119 insertions(+), 95 deletions(-) diff --git a/src/content/docs/en/5x/guide/error-handling.mdx b/src/content/docs/en/5x/guide/error-handling.mdx index c8184815f3..a5b202c501 100644 --- a/src/content/docs/en/5x/guide/error-handling.mdx +++ b/src/content/docs/en/5x/guide/error-handling.mdx @@ -14,6 +14,8 @@ handler so you don't need to write your own to get started. It's important to ensure that Express catches all errors that occur while running route handlers and middleware. +### Errors in synchronous code + Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example: @@ -32,51 +34,24 @@ app.get('/', (req: Request, res: Response) => { }); ``` -For errors returned from asynchronous functions invoked by route handlers -and middleware, you must pass them to the `next()` function, where Express will -catch and process them. For example: - -```js -app.get('/', (req, res, next) => { - fs.readFile('/file-does-not-exist', (err, data) => { - if (err) { - next(err); // Pass errors to Express. - } else { - res.send(data); - } - }); -}); -``` - -```ts -import { type Request, type Response, type NextFunction } from 'express'; - -app.get('/', (req: Request, res: Response, next: NextFunction) => { - fs.readFile('/file-does-not-exist', (err, data) => { - if (err) { - next(err); // Pass errors to Express. - } else { - res.send(data); - } - }); -}); -``` +### Errors in asynchronous code +The recommended way to write asynchronous handlers is with `async` functions. Route handlers and middleware that return a Promise call `next(value)` -automatically when they reject or throw an error. This includes `async` -functions, which always return a Promise. For example: +automatically when they reject or throw an error, and `async` functions always +return a Promise, so their errors reach Express with no extra work. For example: ```js -app.get('/user/:id', async (req, res, next) => { +app.get('/user/:id', async (req, res) => { const user = await getUserById(req.params.id); res.send(user); }); ``` ```ts -import { type Request, type Response, type NextFunction } from 'express'; +import { type Request, type Response } from 'express'; -app.get('/user/:id', async (req: Request, res: Response, next: NextFunction) => { +app.get('/user/:id', async (req: Request, res: Response) => { const user = await getUserById(req.params.id); res.send(user); }); @@ -90,73 +65,33 @@ If you pass anything to the `next()` function (except the string `'route'`), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions. -If the callback in a sequence provides no data, only errors, you can simplify -this code as follows: - -```js -app.get('/', [ - function (req, res, next) { - fs.writeFile('/inaccessible-path', 'data', next); - }, - function (req, res) { - res.send('OK'); - }, -]); -``` +### Working with promise chains -```ts -import { type Request, type Response, type NextFunction } from 'express'; - -app.get('/', [ - function (req: Request, res: Response, next: NextFunction) { - fs.writeFile('/inaccessible-path', 'data', next); - }, - function (req: Request, res: Response) { - res.send('OK'); - }, -]); -``` - -In the above example, `next` is provided as the callback for `fs.writeFile`, -which is called with or without errors. If there is no error, the second -handler is executed, otherwise Express catches and processes the error. - -You must catch errors that occur in asynchronous code invoked by route handlers or -middleware and pass them to Express for processing. For example: +If you build a promise chain instead of using an `async` function, return the +promise from the handler and Express will likewise call `next` automatically +when it rejects: ```js -app.get('/', (req, res, next) => { - setTimeout(() => { - try { - throw new Error('BROKEN'); - } catch (err) { - next(err); - } - }, 100); +app.get('/', (req, res) => { + return Promise.resolve().then(() => { + throw new Error('BROKEN'); // Express will catch this and call next. + }); }); ``` ```ts -import { type Request, type Response, type NextFunction } from 'express'; +import { type Request, type Response } from 'express'; -app.get('/', (req: Request, res: Response, next: NextFunction) => { - setTimeout(() => { - try { - throw new Error('BROKEN'); - } catch (err) { - next(err); - } - }, 100); +app.get('/', (req: Request, res: Response) => { + return Promise.resolve().then(() => { + throw new Error('BROKEN'); // Express will catch this and call next. + }); }); ``` -The above example uses a `try...catch` block to catch errors in the -asynchronous code and pass them to Express. If the `try...catch` -block were omitted, Express would not catch the error since it is not part of the synchronous -handler code. - -Use promises to avoid the overhead of the `try...catch` block or when using functions -that return promises. For example: +If the promise is not returned, Express does not know it exists, and you must +route the error yourself by providing `next` as the final catch handler. +Without it, the rejection would be unhandled and crash the process: ```js app.get('/', (req, res, next) => { @@ -180,18 +115,72 @@ app.get('/', (req: Request, res: Response, next: NextFunction) => { }); ``` -If a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final `.catch`. There, passing `next` as the handler is enough: `.catch` calls it with the error as its first argument, which is exactly the argument `next` expects, so the error reaches Express. +This works because if a callback in the promise chain throws, the chain turns that exception into a rejection, which travels down to the final `.catch`. There, `.catch` calls its handler with the error as the first argument, which is exactly the argument `next` expects, so the error reaches Express. + +### Working with callback APIs -Note that the handler above does not return the promise chain, so Express does not know it exists, and attaching `.catch(next)` is what routes the error. Without the `.catch(next)`, the rejection would be unhandled and crash the process. If you return the promise instead, Express watches it and calls `next` automatically when it rejects, so the `.catch` is no longer needed: +Errors produced by callback-based APIs, such as those in `node:fs`, are not +thrown and are not part of any promise. The callback receives them as its first +argument, and you must pass them to the `next()` function yourself, where +Express will catch and process them. For example: ```js -app.get('/', (req, res) => { - return Promise.resolve().then(() => { - throw new Error('BROKEN'); // Express will catch this and call next. +app.get('/', (req, res, next) => { + fs.readFile('/file-does-not-exist', (err, data) => { + if (err) { + next(err); // Pass errors to Express. + } else { + res.send(data); + } }); }); ``` +```ts +import { type Request, type Response, type NextFunction } from 'express'; + +app.get('/', (req: Request, res: Response, next: NextFunction) => { + fs.readFile('/file-does-not-exist', (err, data) => { + if (err) { + next(err); // Pass errors to Express. + } else { + res.send(data); + } + }); +}); +``` + +If the callback in a sequence provides no data, only errors, you can simplify +this code as follows: + +```js +app.get('/', [ + function (req, res, next) { + fs.writeFile('/inaccessible-path', 'data', next); + }, + function (req, res) { + res.send('OK'); + }, +]); +``` + +```ts +import { type Request, type Response, type NextFunction } from 'express'; + +app.get('/', [ + function (req: Request, res: Response, next: NextFunction) { + fs.writeFile('/inaccessible-path', 'data', next); + }, + function (req: Request, res: Response) { + res.send('OK'); + }, +]); +``` + +In the above example, `next` is provided as the callback for `fs.writeFile`, +which is called with or without errors. If there is no error, the second +handler is executed, otherwise Express catches and processes the error. + You could also use a chain of handlers to rely on synchronous error catching, by reducing the asynchronous code to something trivial. For example: @@ -235,6 +224,41 @@ synchronous error handler will catch it. If you had done this processing inside the `readFile` callback, then the application might exit and the Express error handlers would not run. +Finally, for asynchronous code that provides no error-first callback, such as a +timer, catch errors inside the asynchronous code itself and pass them to +Express: + +```js +app.get('/', (req, res, next) => { + setTimeout(() => { + try { + throw new Error('BROKEN'); + } catch (err) { + next(err); + } + }, 100); +}); +``` + +```ts +import { type Request, type Response, type NextFunction } from 'express'; + +app.get('/', (req: Request, res: Response, next: NextFunction) => { + setTimeout(() => { + try { + throw new Error('BROKEN'); + } catch (err) { + next(err); + } + }, 100); +}); +``` + +The above example uses a `try...catch` block to catch errors in the +asynchronous code and pass them to Express. If the `try...catch` +block were omitted, Express would not catch the error since it is not part of the synchronous +handler code. + Whichever method you use, if you want Express error handlers to be called in and the application to survive, you must ensure that Express receives the error.