Node.jsLesson 9 of 13

Error Handling in Node.js

A server that crashes on the first bad input is useless. Error handling is how you keep Node.js programs running when something fails. Files get missing, requests arrive malformed, and databases go offline. The difference between a fragile script and a solid one is how it responds to those failures.

A server that crashes on the first bad input is useless. Error handling is how you keep Node.js programs running when something fails. Files get missing, requests arrive malformed, and databases go offline. The difference between a fragile script and a solid one is how it responds to those failures.

The tools are the same as browser JavaScript: try catch for synchronous code, and catch or .catch for promises. But Node.js adds its own traps. Callbacks receive an error as the first argument instead of throwing it, and async code that rejects without a handler triggers the global unhandledRejection event, which can silently kill your process.

The golden rule is to handle errors where they can occur, not at the top of the program. Wrap file reads, network calls, and database queries in try catch or attach a catch handler. Log what happened with enough detail to debug it later, then decide: recover, retry, or fail with a clear message. Never let an error just disappear.

Errors in callbacks

Older Node.js APIs pass errors to callbacks as the first argument. The callback checks whether the error is set and reacts. Skipping the check is the classic beginner mistake: the file read fails silently and the code breaks later for reasons you cannot see.

JS
const fs = require("fs");

fs.readFile("missing.txt", "utf8", (err, data) => {
  if (err) {
    console.error("Read failed:", err.message);
    return;
  }
  console.log(data);
});

Errors in promises

With promises, failures travel through the chain and are caught by catch or by try catch around await. A rejected promise that nobody handles triggers unhandledRejection and can take the process down. Always attach a catch at the end of a chain.

JS
fs.promises
  .readFile("missing.txt", "utf8")
  .then((data) => console.log(data))
  .catch((err) => console.error("Failed:", err.message));

Keeping the server alive

One thrown error inside a request handler can crash the whole process if nothing catches it. Wrap handler logic in try catch and answer the client with a 500 response instead of letting the process die. Log the real error for you and send a friendly message to the user.

JS
app.get("/data", async (req, res) => {
  try {
    const data = await loadData();
    res.json(data);
  } catch (err) {
    console.error("Request failed:", err);
    res.status(500).json({ error: "Something went wrong" });
  }
});

Syntax

JS
const fs = require("fs");

async function readConfig() {
  try {
    const data = await fs.promises.readFile("config.json", "utf8");
    return JSON.parse(data);
  } catch (err) {
    console.error("Config problem:", err.message);
    return {}; // fall back to defaults instead of crashing
  }
}

Uncaught Exception Crashes Process

Uncaught errors crash Node — always use try/catch or process.on("uncaughtException")

JS
process.on("uncaughtException", e=>console.error(e))

Good to know

  • Callback APIs pass errors as the first argument; always check it.
  • Promise chains need a catch, or the rejection can crash the process.
  • Wrap request handlers so one bad request does not kill the server.
  • Log enough detail to debug, then respond to the user with a clear message.
Try it Yourself Edit the code, press Run, then check what changed in the Result.
Ready
Code
Result

Practice makes it stick

Open the Playground and build your own page with the tags you just learned.

→ Open Playground