Error handling practices in ExpressJS

Hello everyone. I have recently started using express and was diving deeper into their docs when I came across the line in screenshot below. The page link is this https://expressjs.com/en/advanced/best-practice-performance.html#use-promises What does it mean to handle errors as close to site as possible? does this mean just handle the error in the middleware that it happens in? What if I have multiple controller middlewares like one for validation and data fetching that calls next() to pass data to another middleware responsible for rendering/sending the view? What if when an error happens I want to pass the view the message so it can show it to user.
app.post("sign-in", async (req, res) => {
try {
const doesUserAlreadyExist = await DB.getUser(req.body.user);
if(doesUserAlreadyExist)
throw new DatabaseError("user already exists");
else
next()
}

catch(err) {
res.render("signup-form", {errMessage : err.message})
}
})
app.post("sign-in", async (req, res) => {
try {
const doesUserAlreadyExist = await DB.getUser(req.body.user);
if(doesUserAlreadyExist)
throw new DatabaseError("user already exists");
else
next()
}

catch(err) {
res.render("signup-form", {errMessage : err.message})
}
})
I feel like this is making the middleware that's just supposed to fetch data do something else by having it render on error. And what if I have another middleware before this that throws an error, I would need to put res.render in that too and that will lead to duplication of code. So far I have handled the errors with just app wide error middleware that express recommends but I'm not sure what to do in the scenarios like above.
No description
5 Replies
13eck
13eck2w ago
What does it mean to handle errors as close to site as possible? does this mean just handle the error in the middleware that it happens in?
I'm guessing yes, it means to handle it where it happens, in the middleware. As an example, if you have an error logger then log the error there—don't wait for it to propagate up a few levels before logging.
What if when an error happens I want to pass the view the message so it can show it to user.
I'm not that familiar with how Express works, but there should be a way to catch a thrown error and send that back to the end user somehow. If you're using a promise chain for the HTTP handler then it's as simple as putting it in the .catch() function call—do what you normally would to send data back. The only difference is that you're sending an error message instead of a success message (or whatever the API endpoint normally sends). Though I do find it funny that it's called "use promises" when it's async/await and not Promise.then().catch(), but that's just me :p
Jochem
Jochem2w ago
from the little I remember of express, and just general knowledge of APIs, you'd return the request in the catch block like Beck said, yeah. You'd just change the status code to be the appropriate error one, and provide an error object as the body
Ganesh
GaneshOP2w ago
Yeah I was planning on catching the thrown error then doing res.render like the above example code. My worry was mainly that i would have to duplicate the res render bit if i have multiple middleware that could each throw an error. Tho I guess I can just create a function that takes response object, error and renders the proper view Thank you both for confirming that the docs mean to handle the error in middleware.
glutonium
glutonium2w ago
in my backend the way i have it, is when there is an error , say in user registration controller where username format is invalid, i would simply throw new Error() or extend the Error class to make a custom ApiError class if i want some extra props and throw that. i have an error handler middleware which basically when an error is thrown , catches it, and makes a new api response with 500 status code by default otherwise what is provided (this is where extending Error class comes in handy cause u can add an extra status pop) and json body with msg and the status code and success: false (just personal pref) now i also make a helper function to wrap controllers in try catch (asyncHandler) where in the catch block i am dong next(error)
errorHandler middleware
import { NextFunction, Request, Response } from "express";
import { ZodError } from "zod";

export const errorHandler = (err: any, _: Request, res: Response, __: NextFunction) => {

console.error(err);
if (err instanceof ZodError) {
res.status(400).json({
success: false,
message: "Validation error",
errors: err.errors.map(error => ({
path: error.path.join("."),
message: error.message,
})),
});
return;
}

res.status(err.status || 500).json({
success: false,
message: err.message || 'Internal Server Error',
});
};
import { NextFunction, Request, Response } from "express";
import { ZodError } from "zod";

export const errorHandler = (err: any, _: Request, res: Response, __: NextFunction) => {

console.error(err);
if (err instanceof ZodError) {
res.status(400).json({
success: false,
message: "Validation error",
errors: err.errors.map(error => ({
path: error.path.join("."),
message: error.message,
})),
});
return;
}

res.status(err.status || 500).json({
success: false,
message: err.message || 'Internal Server Error',
});
};
asyncHandler
import { Request, Response, NextFunction } from "express";

// so this is an async handler function and here's what it does
// takes a async callback function
// wraps in a try catch block
// returns the new function where the call back is wrapped in a try catch
// doing so we dont need to wrap every async func in a try catcn and instead just pass into this function
//
// now this is going to be used for wrappign the controllers
// these controllers r sent to the router http methods (router.path().get() || router.path().post())
// the http verb methods (get, post....) pass the req, res and next arguments
// hence u see those as parameter here in the asyncHandler
//
// so basically this just takes an async func and returns it in a new func wrapped in try catch
export function asyncHandler(
cb: (req: Request, res: Response, next: NextFunction) => Promise<any>
) {
return async function(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
await cb(req, res, next);
} catch (error) {
next(error);
}
}
}
import { Request, Response, NextFunction } from "express";

// so this is an async handler function and here's what it does
// takes a async callback function
// wraps in a try catch block
// returns the new function where the call back is wrapped in a try catch
// doing so we dont need to wrap every async func in a try catcn and instead just pass into this function
//
// now this is going to be used for wrappign the controllers
// these controllers r sent to the router http methods (router.path().get() || router.path().post())
// the http verb methods (get, post....) pass the req, res and next arguments
// hence u see those as parameter here in the asyncHandler
//
// so basically this just takes an async func and returns it in a new func wrapped in try catch
export function asyncHandler(
cb: (req: Request, res: Response, next: NextFunction) => Promise<any>
) {
return async function(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
await cb(req, res, next);
} catch (error) {
next(error);
}
}
}
Ganesh
GaneshOP2w ago
Yeah this is what I have been doing up until now sans the async handle because express 5 automatically passes thrown error, rejected promise to next in an async function If I want to send the error to the templating engine (EJS in this case) the centralized error middleware probably won't work Or it would need multiple if else statments select which template/view to render

Did you find this page helpful?