Carol Skelly
Carol Skelly

Reputation: 362880

Node.js and Express 3: Set app.locals that are loaded asynchrously

I am using Node.js and Express 3.0, and I'm pretty clear on the difference between app.locals and res.locals...

I'd like to set some app.locals variables ('newest' and 'popular') that I want to reference globally in any of my EJS templates. The data for these variables come from a database (CouchDB/Cradle) and is also accessible via one of my app.routes (ie: /myappdata) which simply does as res.json to expose my application data from database.

So what's the best approach for loading this stuff once into my app.locals? Should I create a middleware function with app.use() before my router. How can I ensure that the database doesn't get called on every request and only when my app starts? How should I do async callback in app.use()?

I've also tried directly setting app.locals(), but it seems that 'newest' and 'popular' vars are "undefined" to my templates at certain times. (Maybe something is stepping on the app.locals??)

Here is my 'app.js' returned to server.js on startup:

var express = require('express'),
    engine = require('ejs-locals'),
    conf = require('./conf'),
    cradle = require('cradle').(conf.databaseConn),
    app = express();

exports.init = function(port) {

    app.locals({
        _layoutFile:'layout.ejs',
        newest:{}, // like to set this from db once
        popular:{} // like to set this from db once
    });

    app.configure(function(){
        app.set('views', __dirname + '/views');
        app.set('view engine', 'ejs');

        app.use(express.compress());

        app.use(express.static(__dirname + '/static'));

        app.use(express.bodyParser());
        app.use(express.cookieParser());
        app.use(express.cookieSession({cookie:{path:'/',httpOnly:true,maxAge:null},secret:'blh'}));

        app.use(function(req, res, next){
            res.locals.path = req.path;
            res.locals.user = req.session.user;
            next();
        });

        app.use(app.router);
    });

    app.engine('ejs', engine);

    app.configure('development', function(){
        app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 
    });

    app.configure('production', function(){
        app.use(express.errorHandler()); 
    })

    app.use(function(err, req, res, next){
        res.render('500.ejs', { locals: { error: err, path:"" },status: 500 }); 
    });

    var server = app.listen(port);
    console.log("Listening on port %d in %s mode", server.address().port, app.settings.env);
    return app;

}

Thanks for any suggestions.

Upvotes: 3

Views: 3320

Answers (1)

robertklep
robertklep

Reputation: 203529

You could create a middleware which checks if app.locals.newest (/.popular) is defined; if so, just call next() immediately; if not, perform the database query, store the result in app.locals, and call next() (that's how you create an asynchronous middleware). If implemented that way, the queries will be performed during the first (and only the first) request.

An alternative would be to perform the queries at startup time, before you call app.listen(). That way, you're sure the variables are loaded when the server starts listening for requests.

To coordinate multiple asynchronous database queries, you could use a module like async or queue.

Upvotes: 6

Related Questions