3

I am creating the middleware to be called after app.router and I need to access the data that was stored in res.locals object by the route middleware and route handler.

//...
app.use(app.router);
app.use(myMiddleware);
//...

app.get('/', function(req, res) {
    res.locals.data = 'some data';
});

function myMiddleware(req, res, next) {
    if (res.locals.data)
        console.log('there is data');
    else
        console.log('data is removed'); // that's what happens
}

The problem is that all properties of res.locals become empty after app.router.

I tried to find the place where express or connect cleans res.locals to somehow patch it but so far I can't find it.

The only solution I see at the moment is to abandon the idea of putting this logic in a separate middleware and put it in route-specific middleware, where res.locals is available, but it will make the system much more interconnected. Also I have many routes where route middleware does not call next (when res.redirect is called), so I will have to do many changes to make it work. I'd very much like to avoid it and put this logic in a separate middleware, but I need to access the data that was stored in res.locals.

Any help really appreciated.

esp
  • 7,314
  • 6
  • 49
  • 79
  • 1
    I don't understand why you put it after the app.router. Everybody suggest to put it before (http://stackoverflow.com/a/12571474/229087) I hope it helps you. – Aito Apr 03 '13 at 16:23
  • 1
    @Aito Because the logic of this middleware has to be executed after the route is processed, not before. It uses the information prepared by routes that was available in res.locals. – esp Apr 03 '13 at 16:25
  • Can you explain more about the logic that requires res.locals after the router? It may be that res.locals (normally used to pass data to templates for rendering) may not be suited to your "routing" use case. – Carol Skelly Apr 03 '13 at 16:35
  • The logic is similar to logging, but it logs successful results of routes on the "business" :) level of the application rather than routes themselves... To be more specific, I am making achievements/badges for our little social game for stock market investors: http://SheepOrPig.com . The solution is below from @JonathanLonowski. – esp Apr 03 '13 at 16:57

1 Answers1

5

You can possibly bind it before, but have it act after. The logger middleware is an example of this.

app.use(express.logger('tiny'));
app.use(myMiddleware);
app.use(app.router);

function myMiddleware(req, res, next) {
    var end = res.end;
    res.end = function (chunk, encoding) {
        res.end = end;
        res.end(chunk, encoding);

        if (res.locals.data)
            console.log('there is data');
        else
            console.log('data is removed');
    };

    next();
}

app.get('/', function (req, res) {
    res.locals.data = 'some data';
    res.send('foo'); // calls `res.end()`
});

Requesting / results in:

GET / 200 3 - 6 ms
there is data
GET /favicon.ico 404 - - 1 ms
data is removed
Jonathan Lonowski
  • 121,453
  • 34
  • 200
  • 199
  • That seems to be very cool... Let me see if it works... Am I right to understand that res.end is called by res.render and res.redirect too? And do you know what happens with res.locals? Does res.end cleans it somehow? I can't see it in source code... – esp Apr 03 '13 at 16:36
  • @esp Yeah, `res.render()` and `res.redirect()` both call `res.end()`. The browser won't typically be able to act on the response without `res.end()` being called (exception: Ajax with [long polling](http://en.wikipedia.org/wiki/Push_technology#Long_polling)). And `res.locals` shouldn't ever be "*cleaned*;" just eventually garbage collected. But, in case it's relevant, browsers will often send other requests (e.g., `GET /favicon.ico`) for which `res.locals.data` may not be set. – Jonathan Lonowski Apr 03 '13 at 16:41
  • And if I am not missing something I can use more than one middleware this way and the last added will call all previous and the first will call actual res.end, so the order of their execution will be the same as they were added... Correct? – esp Apr 03 '13 at 22:18
  • But how do I make the second middleware wait for I/O of the first middleware when the first can't use next()... I'm getting lost again. – esp Apr 03 '13 at 22:25
  • @esp This pattern works best with synchronous tasks or parallel asynchronous tasks. If you need to chain asynchronous tasks, it would probably be best to combine them into 1 middleware and use a control flow library -- [`async`](https://npmjs.org/package/async), [`queue`](https://npmjs.org/package/queue), or similar. – Jonathan Lonowski Apr 03 '13 at 22:32
  • I see, thanks... Maybe it'll be ok with asynchronous parallel... Somehow I managed without any control flow library so far, I was abusing route middlewares big time:) Only looked at step, but didn't use it. Will check those out, probably I can have different middlewares adding stuff to be done to the same queue. Thanks. – esp Apr 03 '13 at 22:59