I have an isomorphic react app and I would like to somehow pass state between express middleware.
I have the following express route that handles form submission:
export const createPaymentHandler = async (req: Request, res: Response, next: NextFunction) => { const { field } = req.body; if (!paymentType) { res.locals.syncErrors = { field: 'some error.' }; next(); return; } try { const { redirectUrl } = await makeRequest<CreatePaymentRequest, CreatePaymentResponse>({ body: { paymentType }, method: HttpMethod.POST }); res.redirect(redirectUrl); } catch (err) { error(err); res.locals.serverError = true; next(); } };
The next middleware is handling the rendering.
At the moment I am using res.locals
, is there a better way or a recognised pattern?
4 Answers
Answers 1
Because your handler is async, you need to pass the err
into next
, like so:
next(err);
In order for your middleware to process the error, instead of it being picked up by the default error handler, you need to have four parameters:
app.use((err, req, res, next) => { // handle the error })
It's also worth noting that error handlers need to be specified after other middleware. For your case, it might make sense to use a normal "success" middleware alongside an error handler, rather than combining the two into one middleware.
Finally, keep in mind that passing err
as a parameter is specific to error handlers. If you just want to pass some data into your next middleware, you would do that by modifying the req
:
req.x = 'some data' next()
Then, the next middleware's req
parameter will have the data you set.
Further reading: https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
Answers 2
IMO your question is more about passing some data to the next middleware. Since the rendering logic is handled by the next middleware, the express route shouldn't be concerned by how the data is being used. Your approach looks fine.
res.locals
is the recommended way of passing data to the next middleware. From the docs:
This property is useful for exposing request-level information such as the request path name, authenticated user, user settings, and so on.
Also, since the variables added will be scoped to the current request, thus the data will only be available for the current request's lifecycle. Perhaps you can set a convention of adding a state
key on the res.locals
to store all your state variables, but the current approach would also work fine.
Answers 3
If it's passing lightweight information to the next middleware for rendering purposes then applying res.locals
is fine. However, you might want to look into custom error-handling
for general errors, such as internal error.
Consider the following error handling
function notFoundHandler(req, res, next) { res.status(404).render('notFoundPage', { error: '404 - not found' }); } function badRequestHandler(err, req, res, next) { res.status(400).render('badRequestPage', { error: 'Bad request' }); } function errorHandler(err, req, res, next) { res.status(err.status || 500).render('errorPage', { error: 'Internal server error' }); } app.use(notFoundHandler); app.use(badRequestHandler); app.use(errorHandler);
Now instead of passing error details to the next middleware you would simple let it flow to the error handlers, e.g.
export const createPaymentHandler = async (req: Request, res: Response, next: NextFunction) => { const { field } = req.body; if (!paymentType) { res.status(400); return next(); // This will hit the Bad Request handler } try { const { redirectUrl } = await makeRequest < CreatePaymentRequest, CreatePaymentResponse > ({ body: { paymentType }, method: HttpMethod.POST }); res.redirect(redirectUrl); } catch (err) { res.status(500); return next(err); // This will hit the Error Handler } };
Answers 4
res.locals
is a standard way to pass data to the next middleware in the scope of the current request. Since your use case is around the current request, it makes sense to do so.
At the same time, the standard way to handle errors is to pass the error to the next middleware.
next(err);
Then you can handle the error scenario from the error handler. However, for an isomorphic react app, this would make things harder. So if you decide to go down that path, I would suggest you to use a custom error like PaymentError
by extending Error
. This would make even more sense since you are already using Typescript.
However, when you actually think about this scenario, when the error is not a request error, from the point of view of the react app, it is a special state/property of rendering. Thus I suggest the following hybrid approach.
- If the error is of high priority, that is, if the error should stop rendering the expected content and fallback to a special page, use the
next(err)
approach. - If the error should just be part of the state report, then use the
res.locals
approach.
0 comments:
Post a Comment