404 Handlers

A Catch-All When Nothing Else Matches

404 Handlers

Add a catch-all route last. It runs only when no earlier route matched — a clean place to return 404.

2 min read Level 1/5 #express#routing#404
What you'll learn
  • Add a final fallback
  • Differentiate API vs page 404s
  • Place it correctly relative to error middleware

When no route matches a request, Express’s default response is a plain-text “Cannot GET /…”. Replace that with your own 404 handler mounted last.

The Catch-All

app.get("/users", listUsers);
app.post("/users", createUser);
// ... more routes

app.use((req, res) => {
  res.status(404).json({ error: "not found", path: req.path });
});

Mount after all routes. Express only reaches it when nothing else handled the request.

API vs Page 404s

Split the 404 by accept type:

app.use((req, res) => {
  if (req.accepts("html")) {
    return res.status(404).sendFile(path.resolve("public/404.html"));
  }
  res.status(404).json({ error: "not found" });
});

API clients get JSON; browsers get a styled HTML page.

With Error Middleware

The order matters: 404 catch-all, then the error middleware.

// routes
app.use(routes);

// 404
app.use((req, res) => {
  res.status(404).json({ error: "not found" });
});

// errors (must be 4-arg)
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({ error: "internal" });
});

Be Careful With app.use vs app.all

app.all("*splat", handler) works too, but app.use(handler) is simpler and doesn’t require a path pattern. Either is fine.

Don’t 404 Twice

Some bad patterns put the 404 handler inside individual route handlers AND globally. Pick one place — usually global.

End of Chapter

Next chapter: middleware — the request pipeline that powers everything.

Middleware Intro →