node.jsexpressroutesnode.js-connect

Using NodeJS as an HTTP Server- How to avoid spagetti?


I'd like to use nodejs as a light webserver for a project I'm working on. I'm mostly a PHP programmer so the way I'd do this is different than what I'm used to.

In this application i'm using angular to request some data. I want to format this as "/get/some_data". On my node end I'm using connect and pointing it statically to my http dir.

Now when I make a request for some data it seems the only thing people recommend is looking for that request and passing it back. This is a lot less organized than a file-structure like I'm used to where each file handles the request.

var connect = require("connect");
var app = connect();

app.use(connect.static("/my_http_dir"));
app.use(function(req, res)
{
    if(req.originalUrl == "foo")
    {
        // do something relating to foo
    }
    else if(req.originalUrl == "bar")
    {
        // do something relating to bar
    }
    else
    {
        // do nothing
    }
});

connect.createServer(app).listen(80);

So what is used to organize this? Do I need to make a get handler, put handler, delete handler, post handler, etc- and pass the "files" to a switch statement?


Solution

  • In addition to what Jim has said, I would also add another patterns which can make your code cleaner.

    Verify route parameters in separate functions

    If you use a route with a parameters, say, app.get('/blog/:slug', ... then you can use app.param in order to verify if a given slug exist and perform necessary actions (send 404 or pass your request to another route if there is no that slug in your database or load a blog post identified by the given slug) e.g.

     function slugParam (req, res, next, slug) {
        Post.findBySlug({ slug: slug }, function (err, post) {
            if (err) return res.send(500, 'SERVER ERROR');
            if (!post) return res.send(404);
    
            res.locals.post = post; // you will be able to access 'post' in your template
            next();
        })
    }
    

    And when you setup you application you have to call app.param e.g.

    app.param('slug', slugParam);
    

    With that approach you won't need to check in your request handler if a post identified by a give slug exist - very clean approach.

    Split request handler function into middleware functions

    You can also split your request handler function into several smaller (cleaner) functions and call them sequentially (actually express will call them for you). Let's say, you build a discussion forum and you have a route like this:

    app.get('/forum/:fid/thread/:tid', function(){
        // ...
    })
    

    You would need to handle forum and thread loading in a single function - very quickly you will see spaghetti code (especially that both operations will be executed asynchronously). In order to fix it you can split that functions into independent ones responsible for one a single functionality e.g. load forum, load thread, etc.

    Example:

    function loadFormum (req, res, next) {
        Forum.load(/* params here */, function (err, forum) {
            if (err) return res.send(500);
            if (!forum) return res.send(404);
    
            res.locals.forum = forum;
            next();
        })
    }
    
    function loadThread (req, res, next) {
        // you can use forum from res.locals.forum here 
        Forum.findThread(/* params here */, function (err, thread) {
            if (err) return res.send(500);
            if (!thread) return res.send(404);
    
            res.locals.thread = thread;
            next();
        })
    }
    
    app.get('/forum/:fid/thread/:tid', loadFormum, loadThread, function(){
        // cleaner code
    })
    

    Or

    var forumMiddleware = [loadForum, loadThread]
    
    app.get('/forum/:fid/thread/:tid', forumMiddleware, function(){
        // cleaner code
    })
    

    With that approach, functions may depend on each other i.e. loadForum loads a model, which is then used by loadThread. You may use as many middleware functions as you want to in order to keep your code clear.

    I hope that will help.