Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions src/content/docs/en/4x/guide/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,39 @@ 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 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) => {
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);
}
});
```

```ts
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.
<Alert type="info">

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.

</Alert>

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
Expand Down Expand Up @@ -180,9 +190,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:
Expand Down Expand Up @@ -219,7 +227,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
Expand Down Expand Up @@ -424,7 +432,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) {
Expand Down
218 changes: 125 additions & 93 deletions src/content/docs/en/5x/guide/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);
}
});
});
```
### Errors in asynchronous code

```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);
}
});
});
```

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:
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, 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);
});
Expand All @@ -90,99 +65,121 @@ 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:
### Working with promise chains

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('/', [
function (req, res, next) {
fs.writeFile('/inaccessible-path', 'data', next);
},
function (req, res) {
res.send('OK');
},
]);
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('/', [
function (req: Request, res: Response, next: NextFunction) {
fs.writeFile('/inaccessible-path', 'data', next);
},
function (req: Request, res: Response) {
res.send('OK');
},
]);
app.get('/', (req: Request, res: Response) => {
return Promise.resolve().then(() => {
throw new Error('BROKEN'); // Express will catch this and call next.
});
});
```

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 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) => {
setTimeout(() => {
try {
Promise.resolve()
.then(() => {
throw new Error('BROKEN');
} catch (err) {
next(err);
}
}, 100);
})
.catch(next); // Errors will be passed to Express.
});
```

```ts
import { type Request, type Response, type NextFunction } from 'express';

app.get('/', (req: Request, res: Response, next: NextFunction) => {
setTimeout(() => {
try {
Promise.resolve()
.then(() => {
throw new Error('BROKEN');
} catch (err) {
next(err);
}
}, 100);
})
.catch(next); // Errors will be passed to Express.
});
```

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.
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.

Use promises to avoid the overhead of the `try...catch` block or when using functions
that return promises. For example:
### Working with callback APIs

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, next) => {
Promise.resolve()
.then(() => {
throw new Error('BROKEN');
})
.catch(next); // Errors will be passed to Express.
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) => {
Promise.resolve()
.then(() => {
throw new Error('BROKEN');
})
.catch(next); // Errors will be passed to Express.
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err); // Pass errors to Express.
} else {
res.send(data);
}
});
});
```

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 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:
Expand Down Expand Up @@ -219,14 +216,49 @@ 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
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.

Expand Down Expand Up @@ -424,7 +456,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) {
Expand Down
Loading