Friday, March 7, 2014

Modular Express App Design

The Wild West Of Node.js

I love Node. I love the minimalist nature of it, I love that it's new enough that nobody has a "best practices" guide yet, and I love that it's javascript. What I don't love is having to make a web server from scratch, so I always start each project with a quick:

npm install --save express

Okay, so you've got your app ready to go, and maybe you've even done some small projects with Express, but now you're at a point where you need to do some serious lifting and you'd rather not have your entire business logic lumped into a single app.js file. If your journey is anything like mine, maybe you've even tried the dozen or so recommendations for structuring your app, including crazy things like using Grunt to compile everything down to a single app.js file before deployment.

None of the solutions I tried seemed to address my specific use-cases. Several of the better approaches that I tried actually came from the Ruby community, and they were all very well founded, but they weren't very Node. That is, they hacked and worked around Node and Express instead of using its features. Some solutions even required frameworks for injecting modules instead of using Node's built-in require(). I knew there needed to be a better way of doing this.

Modular Design

Require() is great. Requiring a module into your code gives you flexibility to break your code into smaller components and pass them around as needed, or reuse them across projects. So, the real trick comes in figuring out how to break your code up. Cliff Meyers has a great write up on different ways of structuring your front-end Angular app (which is worth a read if you aren't familiar with some of the more common approaches), and I wanted to see what I could do to apply his modular approach to Node. What I really wanted was a way to break my code into features, rather than the more common Model, View, Controller paradigm. For added reusability, I didn't want the components to be aware of their route on the web server. That is, if I made a User component to manage user interaction, I didn't want app.get('/user') anywhere in the module, in case I ran into an issue where I needed to use that same bit of code in an app that already had that route mapped.

tl;dr SHOW ME THE CODE ALREADY

Here's a typical layout for a module for me. There's a lot going on - more than you might think you need - but I like to use this structure because it fits all of my use-cases, and I am a big fan of consistency. So, let's take a look at the user module and see what's happening here.

NERDTree view of our project
node_modules/

Since our goal is to make each module self-contained, we want to package any dependencies with the module itself instead of using global copies. There are some caveats to this that I'll cover in just a moment.

tmp/

For added portability, I like to have any directories that the module is going to access inside the module itself. In this case, instead of figuring out where the system's temporary directory is, I just create one inside the module's path.


routes.js

I know, I'm not going in order, but hopefully this makes more sense.  The routes file is where we put our routing verbs (get, post, put, delete) and the relative paths we want them mounted to. Here's a bare-bones route.js file.
routes.js

A couple of things to pay attention to here. First, we've split our route handlers into their own code for added reusability (multiple routes can now share the same handler). Second - and by far the most important thing happening in this file - we are assigning the result of calling express() into our app variable. It's very important to understand what that means. We aren't creating an instance of Express (unless this is the first time calling Express, sort of. See caveats below). We are, however, attaching our app variable to a global (I know, globals are bad - this isn't quite the same) copy of Express. I'll get into how this works and rationale at the end, so don't start pulling your hair out yet. The very last bit sets this module's exports to our Express app so we can chain things together, which comes in handy later.


handlers.js

I've opted to split the route handlers and controller code into separate files. The rationale for this is that when I'm hacking away at code I've not worked on for months, I always know what goes where based on 1) Does this directly interact with a route 2) What's the function's signature. My handlers will always have the same signature, and it's their job to shift things around and prepare it for our controllers.
handlers.js

In the screenshot here, you'll notice that our getAll controller function is passed req.query, whereas our getById controller function is passed req.params. Our controllers shouldn't care what the source of the data is (decoupled code is happy code). Be sure to set module.exports = handlers at the end of your file!

controller.js

If you've used an MVC framework before then the idea of controllers should be pretty familiar. If not, the best way to think of our code so far is to think of it as a single object functioning as one (think methods and attributes). Our models (which we will get to shortly) are our attributes, and our controllers are our methods. The goal of our routes and handlers is simply to provide an interface for our object. I like to have a consistent signature for my controllers, because consistency makes for more productive coding.

The important thing to note so far is that our controller should be the only place in your code where you touch your models. In many cases, your controllers are just interfaces to add, remove, modify, and delete documents from our model. Like handlers, the most important bit here is to make sure you set module.exports = controller at the end of your file. Failing to do so will cause all sorts of fun errors later.

controller.js

middleware.js

Using the same pattern as our handlers, we can also define what middleware our module would like to expose. I rarely use this, but it's something I like to package into every module in case the need arises. The only difference when coding middleware is that you'll want your function signature to include the next keyword.


models.js

I use mongodb and mongoose in my projects, but you could likely use any database or object store you'd like with very few changes. You've probably noticed a pattern in how our code is being structured: a clear division of concerns in each file. This makes maintaining our code a breeze. Notice on our models how we establish our module.exports. Like the other pieces we've done so far, we want to have our module.exports be an object, but in models I like to round everything up in the end (instead of attaching everything to a models object) since we are returning the parsed mongoose models, not the schemas.
middleware.js


events.js

It's been my experience that anytime I try to require() one controller inside of another, it's only a matter of time before I have a big awful pile of spaghetti code. Instead, I prefer to use a pub/sub system to communicate between controllers. There's a million ways to do this, and a bit out of the scope of this article.


index.js

Our most important, and smallest, bit of code is our index file. All it needs to do is round everything up and re-export it back out. This gives us the ability to simply require the module instead of requiring each component of it. That is, instead of require('moduleName/controller.js') and require('moduleName/routes.js'), we can just require('moduleName')
models.js


Cool? Now What?

That's our module. Pretty swell, right? Okay, so now how do we make use of it and why did we do all of that weird stuff with var app = express()? Well, let's take a look at our server.js file (or app.js or whatever convention you like to use as your main entry in your Node/Express apps). I'm going to assume you have your module in a lib folder like my initial screenshot, and that our module is called user.

server.js

var express = require('express');

var app = express();

var user = require('./lib/user');



app.configure(function() {

  app.use('/user', user.routes);

})

index.js
By using app.use, what we are doing is mounting those routes we've created in routes.js onto /user. This means that if we had defined app.get('/:id') in our routes.js file, it will now be accessible at /user/:id.


Caveats

There are a handful of things you may run into with this method. One of the first things I had issue with was understanding how all of the var app = express() black magic happened. If you aren't familiar with how require() works, all you really need to understand is that require() will cache anything you pull in with it based on the path you pass to it. That's important to understand because it means that if we had done something like var express = require(__dirname + '/node_modules/express') then each module would have its own instance of express (since __dirname will be different for each module), instead of using the global cached version! This is how we are able to mount our routes into our server.js file and have everything properly routed.


That's it?

Yes, that's it. I know, it's a bit of a long-winded write-up for what seems like such a simple idea. I'm amazed by how many approaches there are to this problem, and I encourage you to give each of them a try to see what fits your needs best. Also, please comment and let me know if there is anything I've forgotten to mention or some huge flaw in this approach. Thanks!