Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Async handlers

Many server operations are asynchronous: reading a file, querying a database or another website. A route handler can be an async function and wait for the result with await. In Express 5 an error inside such a function reaches the error handler by itself — wrapping the code in try...catch is not required.

An async route

Say the text of the "About the blog" page is stored in content/about.txt in the project root. Read it in a controller:

// src/controllers/pagesController.js
import { readFile } from 'node:fs/promises';
 
export async function about(req, res) {
  const text = await readFile('content/about.txt', 'utf8');
  res.send(`<h1>About the blog</h1><p>${text}</p>`);
}

And add the route in src/app.js:

// src/app.js
import * as pagesController from './controllers/pagesController.js';
 
app.get('/about', pagesController.about);

/about shows the text from the file. While the file is being read, the server does not stand still and serves other requests.

What happens on an error

Delete or rename content/about.txt and open /about again. readFile() returns a rejected promise, Express 5 notices it and calls the error handler from the previous lesson. The visitor sees "Server error", and ENOENT: no such file or directory appears in the terminal.

How it was in Express 4

In older projects you may see a wrapper like this:

// src/controllers/pagesController.js
export async function about(req, res, next) {
  try {
    const text = await readFile('content/about.txt', 'utf8');
    res.send(`<p>${text}</p>`);
  } catch (error) {
    next(error);
  }
}

Express 4 did not watch promises, and without try...catch the error went unhandled. In Express 5 this code still works, but you do not need to write it.

When try...catch is still needed

try...catch is used when the error should be handled on the spot instead of passed on — for example, to show fallback text:

// src/controllers/pagesController.js
export async function about(req, res) {
  let text = 'A story about the blog is coming soon.';
 
  try {
    text = await readFile('content/about.txt', 'utf8');
  } catch {
    // no file — keep the default text
  }
 
  res.send(`<h1>About the blog</h1><p>${text}</p>`);
}

Now, without the file, the page still opens with "A story about the blog is coming soon."

Common mistake

Express 5 sees only the promise returned by the handler itself. If you forget await, the function finishes before the operation, and the error slips past Express: the process reports an unhandled promise rejection and may exit. Put await before every asynchronous operation whose result or error matters for the response.

Thanks for reading!

Donate to the site — we work hard for you.