Kodokon kodokon.com

Async in practice

Use async/await in your routes and handle asynchronous errors without bringing the server down.

10 min · 3 questions

Open this lesson in Kodokon

Your real routes don't return in-memory arrays: they read files, query a database, call other APIs. All of these operations are asynchronous. You've known async/await since the JavaScript track; let's see how to use them cleanly in a server. First good news: a route function can be async.

JAVASCRIPT
import express from "express";
import { readFile } from "node:fs/promises";

const app = express();

app.get("/report", async (req, res) => {
  const raw = await readFile("report.json", "utf8");
  res.json(JSON.parse(raw));
});

app.listen(3000);

This code works... as long as report.json exists. If the read fails, the promise is rejected and, depending on the Express version, the request stays without a response or the error leaks into the logs. The professional rule: every async route handles its errors, with try/catch and next(error).

JAVASCRIPT
app.get("/report", async (req, res, next) => {
  try {
    const raw = await readFile("report.json", "utf8");
    res.json(JSON.parse(raw));
  } catch (error) {
    next(error);
  }
});

next(error) passes the error to the centralized error handler. It's a middleware with four parameters, declared after all the routes. A single place to log and format errors: that's what sets a maintainable API apart from a weekend project.

JAVASCRIPT
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({
    error: "Internal server error"
  });
});
Always last, after the routes

One last reflex to build: parallelism. Two independent operations chained with two awaits run one after the other. Promise.all launches them together: the response time is that of the slowest, not the sum of the two.

JAVASCRIPT
async function fetchUsers() {
  return [{ id: 1, name: "Ada" }];
}

async function fetchTasks() {
  return [{ id: 1, title: "Review the code" }];
}

app.get("/dashboard", async (req, res, next) => {
  try {
    const [users, tasks] = await Promise.all([
      fetchUsers(),
      fetchTasks()
    ]);
    res.json({ users, tasks });
  } catch (error) {
    next(error);
  }
});

Knowledge check

Make sure you remember the key points of this lesson.

  1. In an async route, what do you do with an error caught by catch?
    • Log it with console.log and carry on
    • Call next(error) to pass it to the error handler
    • Automatically retry the request
  2. Two independent asynchronous calls must run as fast as possible. Which approach do you choose?
    • Chain them with two successive awaits
    • Launch them together with Promise.all
    • Use setTimeout between the two
  3. How does Express recognize an error-handling middleware?
    • By its four-parameter signature, with err first
    • By the function name, which must be errorHandler
    • By its position at the top of the file