Try/Catch for Async Code
Use try/catch with async/await to handle errors gracefully. You can check error properties like err.code or use instanceof to handle different error types.
async function processFile(filename) {
try {
const data = await fs.readFile(filename, "utf-8");
const parsed = JSON.parse(data);
return parsed;
} catch (err) {
if (err.code === "ENOENT") {
console.error("File not found:", filename);
} else if (err instanceof SyntaxError) {
console.error("Invalid JSON in file");
} else {
throw err; // Re-throw unknown errors
}
}
}
Custom Error Classes
Create custom error classes to represent specific error conditions. This makes it easy to throw meaningful errors with status codes and extra context.
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
this.statusCode = 400;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} not found`);
this.name = "NotFoundError";
this.statusCode = 404;
}
}
Express Error Handling
Express error-handling middleware uses a 4-parameter signature. Use the error's statusCode property to send the right HTTP status. In async routes, catch errors and pass them to next().
// Error handling middleware (must have 4 parameters)
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
error: {
message: err.message,
...(process.env.NODE_ENV === "development" && { stack: err.stack })
}
});
});
// Throw errors in async routes
app.get("/api/users/:id", async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) throw new NotFoundError("User");
res.json(user);
} catch (err) {
next(err);
}
});
Golden Rule: Always handle errors. Unhandled Promise rejections and uncaught exceptions crash your Node.js process. Use process.on("unhandledRejection") as a safety net.