Friday, August 24, 2018

pass state around between express middleware in an isomorphic react app

Leave a Comment

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.

  1. 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.
  2. If the error should just be part of the state report, then use the res.locals approach.
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment