Showing posts with label router. Show all posts
Showing posts with label router. Show all posts

Wednesday, September 2, 2015

Refactoring Express Router Middlewares

This past weekend I went back to an old comic to fix some bad code that it used. It had been bothering me for a long time. I knew it was not great when I made it, but since it was a bit of a rush-job (to capitalize on the sudden popularity of a dress color meme) I just lived with it. But no more! It had to be cleaned-up!

The problem with it was that the comic in question stored data about how people interacted with it, but never persisted that data to disk. Every time I re-started the comic application, the previous data was lost. No problem, I just put in a timer that wrote the data as a JSON string to disk every 30 minutes. When the application starts, it reads the file.

But when I was adding this stuff, it bothered me how all the code for the routes for this comic were scattered about in the main app.js file. They really should have been put in a separate file and configured as a router. Again, no problem. I moved the four routes into a separate router module and included it from app.js.

But then I noticed something else that bothered me. I had a bunch of router middlewares now that all used very repetitious blocks of code. Seemed like it was time to make more improvements.

The code originally looked like this.

//------------ set up routes for /data/*

app.use('/data', dataRoutes({
    express: express,
    auth: ensureAuthenticated,
    dataSource: cfact
}));

//------------ set up routes for /memeGen/*

app.use('/memeGen', memeRoutes({
    express: express,
    config: conf
}));

//------------ set up routes for /fb/*

app.use('/fb', fbRoutes({
    express: express,
    config: conf
}));

//------------ set up routes for /colors/*

app.use('/colors', colorRoutes({
    express: express
}));

//------------ set up routes for /images/*

app.use('/images', imgRoutes({
    express: express,
    auth: ensureAuthenticated,
    dataSource: cfact
}));

All the middlewares were passed a very similar object into their constructor, but it was not always identical. Some needed only a single item, some needed three. But they all overlapped. I decided to create the options object only once and pass it to all the middlewares. They could then use whatever they needed and ignore what they didn't. But instead of copying-and-pasting a block of code whenever I want to add another one, I created an array of their names and a function to set everything up. Now I have this:

var express = require('express');

var routers = ["data", "images", "memeGen", "colors", "fb"];

var app = express();

// ... other stuff ...

(function setupRouters() {

    var opts = {
            express: express,
            auth: ensureAuthenticated,
            dataSource: cfact,
            config: conf
    };

    routers.forEach(function(val) {

        try {
            console.log("loading router [" + val + "] ...");
            var r = require("./routers/" + val);
            app.use("/" + val, r(opts));
        } catch (e) {
            console.error("error loading router [" + val + "] - " + e);
        }

    });

})();

With the array of router names configured, I create and call a function which goes over that list and creates each router. It sets each one up with the path to match its name. It's now very easy for me to add new router middlewares.

I always enjoy a good refactoring. Much more than I enjoy taking a picture of myself (see today's comic).

Amphibian.com comic for 2 September 2015

Friday, September 12, 2014

Express Routers as Middleware

Lately I've been developing my web applications in JavaScript, using Node with the Express framework. It's easy to get something thrown together and working very quickly, but like anything else, if you want to maintain it for long it should be well-organized.

Using Routers as Middleware is nice way of partitioning your applications into smaller, easier-to-handle modules that can even be re-used in other projects.

Sometimes you end up with a lot of different routes that have similar prefixes. In my comic application I have things such as

GET  /images
GET  /images/<image>
POST /images
GET  /data/<id>
PUT  /data/<id>
POST /data


...mixed in with a bunch of other routes. All of the /data/* routes are related, and all of the /images/* routes are related. It would be nice to extract them out into their own modules.

We can do this by creating Routers for each set of related paths and using them as Middleware in the main application.

Creating a Router is simple.

var express = require('express');
var myRouter = express.Router();

But how do you use it for something? The Router is a Middleware itself so you use it the same way you use any other Middleware.

var express = require('express');
var app = express();

var myRouter = express.Router();
app.use('/path', myRouter);

Of course, that Router won't really do anything. Let's add a path /cheese that we can GET. Here is a complete app.

var express = require('express');
var app = express();

var myRouter = express.Router();
myRouter.get('/cheese', function(req, res, next) {
    res.send(200, 'cheese is good');
});

app.use('/path', myRouter);

var server = app.listen(3000, function() {
    console.log('listening on port %d', server.address().port);
});

Now we're making progress. If you request /path/cheese you'll get the response "cheese is good." Which it certainly is.

That's great and all, but if you want to really perform some good modularization, the definition of the Router's unique functions should be moved into a separate file. So we would make a new file, cheese.js, that would look something like this.

module.exports = function(express) {

    var myRouter = express.Router();
    myRouter.get('/cheese', function(req, res, next) {
        res.send(200, 'cheese is good');
    });

    return myRouter;

};

And then your main app.js would look like this.

var express = require('express');
var cheese = require('./cheese');
var app = express();

app.use('/path', cheese(express));

var server = app.listen(3000, function() {
    console.log('listening on port %d', server.address().port);
});

And a request for /path/cheese should still tell you that cheese is good.

That's great and all, but here is the real benefit. You can add the same Router for multiple paths. Obviously, you could also reuse your cheese.js module in other applications as well. In the following example, I add the cheese router to both /path and /goat.

var express = require('express');
var cheese = require('./cheese');
var app = express();

var cheeseRouter = cheese(express);

app.use('/path', cheeseRouter);
app.use('/goat', cheeseRouter);

var server = app.listen(3000, function() {
    console.log('listening on port %d', server.address().port);
});

Now you should be able to GET /path/cheese and /goat/cheese. Goat cheese tastes much better than path cheese. Oh, what is path cheese? Well I'm sure you're familiar with leaving a trail of breadcrumbs. If you leave yourself a trail of bits of cheese you have path cheese.

My cheese example is highly simplified but if your module had a number of GETs and POSTs and stuff at different paths, the benefit of breaking it out into a separate file becomes much more clear. It also helps when some routes might need to import another module via require and no other routes need that module. Everything stays neat and self-contained.

Much easier to maintain.

Amphibian.com comic for September 12, 2014