Node.js and Q

To be honest, I have to separate this code into 10 files (configs, models, views, templates) but this is not an article about how to setup node.js project with express.js and mongodb. I just wanted to show how to handle asynchronous requests to mongodb from express.js routes.

So the first piece of sugar is simpleJSONWrapper. It wraps controller method which returns Q-promise (actually i was wrote all this with jquery-deferred but then ported to Q) and prints JSON

The second sweet piece is simpleHTMLWrapper it does almost the same but renders HTML instead of JSON. It also passes an object with resolved promises which was returned by controller method to template engine

And the third method complicatedHTMLWrapper passes to template engine not only resolved but rejected promises


"use strict";

//./app.js
var http = require("http"),
    mongo = require("mongodb"),
    mongoose = require("mongoose"),
    express = require("express"),
    Q = require("q"),
    app = express(),
    Schema = mongoose.Schema;

// config/mongo.js
mongoose.connect("mongodb://localhost:27017/db");

mongoose.connection.on("open", function () {
    console.log("Connected to mongo server.");
});

mongoose.connection.on("error", function (error) {
    console.log("Could not connect to mongo server!");
    console.error(error);
});

// models/user.js
mongoose.model("User", new Schema({
    first_name: String,
    last_name: String,
    email: String,
    password: String
    articles: [
        {type: Schema.Types.ObjectId, ref: "Article"}
    ]
}, { versionKey: false }));

//models/article.js
mongoose.model("Article", new Schema({
    author: {type: Schema.Types.ObjectId, ref: "User"},
    published: {type: Date, default: Date.now}
    text: String,
    title: String
}, { versionKey: false }));

//utils/helper.js
function simpleJSONWrapper(method) {
    return function (request, response) {
        var params = ["limit", "skip", "sort"];
        method(Object.keys(request.query).length === 0 ? (Object.keys(request.body).length === 0 ? request.params : request.body) : _.omit(request.query, params), _.pick(request.query, params), request)
            .then(function (result) {
                response.json(result);
            })
            .fail(function (error) {
                response.send(500, error);
            })
            .done();
    };
}

function simpleHTMLWrapper(method) {
    return function (request, response, next) {
        var result = method(request, response, next),
            keys = Object.keys(result);
        Q.all(keys.map(function (key) {
                return result[key];
            }))
            .then(function (results) {
                var params = {};
                keys.map(function (element, index) {
                    params[element] = results[index];
                });
                response.render(method.name.replace("_", "/") + ".html", params);
            })
            .fail(function (error) {
                next(error);
            });
    };
}

function complicatedHTMLWrapper(method) {
    return function (request, response, next) {
        var result = method(request, response, next),
            keys = Object.keys(result);
        Q.allSettled(keys.map(function (key) {
                return result[key];
            }))
            .then(function (results) {
                var params = {};
                keys.map(function (element, index) {
                    params[element] = results[index].state === "fulfilled" ? results[index].value : new Error(results[index].reason);
                });
                response.render(method.name.replace("_", "/") + ".html", params);
            })
            .done();
    };
}

//controllers/user.js
function getUsers(params) {
    var query = User.find(params);
    return Q.nbind(query.exec, query);
}

//configs/express.js
app.set("port", process.env.PORT);
app.set("jsonp callback", true);

app.use(express.logger("dev")); // "default", "short", "tiny", "dev"
app.use(express.bodyParser());

//routes/user.js
app.get("/getUsers.json", helper.simpleJSONWrapper(getUsers));

app.get("/getUsers.html", helper.simpleHTMLWrapper(function path_to_template(request, response, next) {
    // variable `users` will be available in template
    return {
        users: getUsers() 
    };
}));

// name of this function is used to generate path to template
app.get("/getUsersAndArticles.html", helper.complicatedHTMLWrapper(function path_to_template(request, response, next) {
    // `someStaticData`, `users`, `articles` will be available in template
    return {
        someStaticData: { key: "value" },
        users: getUsers,
        articles: function () {
            var deferred = Q.defer();
            Article.aggregate({
                    $match: request.query.match
                }, {
                    $sort: request.query.sort
                }, {
                    $limit: request.query.limit
                }, deferred.makeNodeResolver());
            return deferred.promise;
        }
    };
}));

//./app.js
//Starts the server
http.createServer(app).listen(app.get("port"), function () {
    console.log("Express server listening on port " + app.get("port"));
});

process.on("uncaughtException", function (exception) {
    console.error(exception.stack);
});

Now this code is a part of G.O.A.T project