2

In express applications, what is the right way to send error messages to views ?

(we assume that the code is synchronous)

Example 1 :

// Route
Router.route('/')
  .get(controller.getIndex);

// Controller
Controller.getIndex = function (req, res, next) {
  doSomething(function (err, datas) {
    if (err) { res.locals.err = 'error message'; }
    // ...
  });
  res.render(/* view */);
};

// View 
<p class="error">${ out.global.err }</p>

Here no problem : if there is an error, I store a message in the response and display it in the view.

Example 2 :

// Route (with multiple middlewares)
Router.route('/')
  .get(firstMiddleware, otherMiddleware/*, otherMiddleware2, etc */, controller.getIndex);

// firstMiddleware (always need to be called)
firstMiddleware = function (req, res, next) {
  doAlsoSomething(function (err, datas) {
    if (err) { res.locals.err = 'error message'; }
    // ...
  });
  next();
};

// otherMiddleware, otherMiddleware2 (need to be called only if no errors)
otherMiddleware = function (req, res, next) {
  next();
};

// Controller (need to be called only if want to display the view)
Controller.getIndex = function (req, res, next) {
  doSomething(function (err, datas) {
    if (err) { res.locals.err = 'error message'; }
    // ...
  });
  res.render(/* view */);
};

// View 
<p class="error">${ out.global.err }</p>

In this example, otherMiddleware will be always called. It is not what I want, it should stop the middlewares cycle in case of errors. Then if errors need to be display on the view it should called Controller.getIndex() with error messages like in example 1. If errors have their own views (as 404, 500...), it should not call the Controller.getIndex(), but the appropriate view. In any cases, if an error occurs in otherMiddlewareX, the middlewares "after" (otherMiddlewareX + 1, otherMiddlewareX + 2 ... otherMiddlewareN) should not be called.

Anyone can help me to find a "right" way to do that ?

EDIT : similar problem similar stack overflow question

Possible answers (i can give example of implementations if necessary) :

  • Put a condition if (res.locals.err) in each middleware to jump to the next one in case of errors by calling next();. That's not very convenient.
  • Call an error-handling middleware with next('error message'); but it is hard to manage every redirection in one function (this is the solution i use)
  • Use next('route'); with a route duplication each time to manage error (similar as error-handling middleware solution).
  • Use res.redirect(); and flash messages (session, query string, db, cookie...). If I use this solution, I need to make it stateless. I could do it with query string for example, but this is also not very convenient to me. Furthermore, it doesn't really answer the question directly but more the question how to pass message between requests in REST API.

There is probably a way to do that easily with express app, but I can't arrive to figure out what's the right way. Could you please help me ?

Thank you in advance.

EDIT : More details about possible answers (third solution).

With next('error message'); I have an error-handler middleware where I need to display the good view. The view can be a 500, 404, 406... but also a 200 with an error message on it. Firstly, I need to give the status code to this middleware, then if it's a 200 I need to call the view that corresponds to the error. The problem is that I don't always know which view to call. I can have a POST on /auth that needs to be redirected to GET /login. I need to "hardcode" it and can't write the same code for every situations or i need to pass the view/controller in each next('error message'); call. Even the redirection is hard, because sometimes I need to call a controller, but sometimes I needs to go through another middleware cycle by calling the route, not the controller and the only way I found is return app._router.handle(req, res, next); which looks more as a "hack" that an official solution. If i redirect with res.redirect(); i came back to my second possible answer where i need to pass some flash messages. To finish, this is the solution i use (next(err)), but i was hoping for a better way.

ElJackiste
  • 4,011
  • 5
  • 23
  • 36
  • On your third suggestion, what do you mean by "but it is hard to manage every redirection in one function"? – Murilo Sep 24 '18 at 18:38
  • I mean that i have a lot of different cases to manage and it's hard for me to find a solution which works for every situations without "hardcode" it. I will give more details in my post. – ElJackiste Sep 24 '18 at 18:47

2 Answers2

3

I'm not sure if I understood your whole problem but I would use and error handling middleware like this:

// firstMiddleware
var firstMiddleware = function (req, res, next) {
  doAlsoSomething(function (err, datas) {
    if (err) { next('error message'); }
    // ...
    next(); // If err is true calls renderView
  });
};

// otherMiddleware
var otherMiddleware = function (req, res, next) {
  // Do stuff only if no error
  next();
};

// Controller
Controller.getIndex = function (req, res, next) {
  doSomething(function (err, datas) {
    if (err) { next('error message'); }
    // ...
    next(); // If err is true calls renderView
  });
};

// This middleware runs after next('error message')
var handleError = function (err, req, res, next) {
  res.locals.err = err.message;
}

// This middleware always runs
var renderView = function (req, res, next) {
  res.render(/* view */);
}

// Route
Router.route('/')
  .get(firstMiddleware, otherMiddleware/*, otherMiddleware2, etc */, controller.getIndex, handleError, renderView);
Murilo
  • 173
  • 1
  • 4
  • That's one solution, thank you. The thing annoying with this solution is that i need to put the `handleError` in each route. – ElJackiste Oct 19 '18 at 18:08
  • I updated the answer to support async callbacks in `doAlsoSomething` and `doSomething`. For some reason I forgot to assume they were async. I'm sorry about that. – Murilo Jul 29 '19 at 15:38
0

If you're only using otherMiddleware once, get rid of it, then move its content to the else block of the previous middleware:

var checkForm = function (req, res, next) {
  validationResult(function (err, datas) {
    if (err) { res.locals.err = 'error message'; }
    else {
      // Do stuff only if form is valid
      // otherMiddleware code
    }
    // ...
  });
  next();
};
Murilo
  • 173
  • 1
  • 4
  • Hey thank you for your answer. I am not using `otherMiddleware` only in this situation. It is used in others middlewares cycles. In fact, as you can see in my examples, you even can have few middlewares. You can totally have : `.get(checkForm, otherMiddleware1, otherMiddleware2, otherMiddleware3, otherMiddleware4, otherMiddleware5, controller.getIndex)` with each middleware doing different things and which can be used in other routes. `checkForm` is just an example, but you can replace it by `otherMiddleware0` if you want ;) – ElJackiste Sep 24 '18 at 09:40