Know Your Abstractions
One trait that separates great developers from average developers is the ability to figure out how to learn what they don’t know. Often this means taking basic building blocks and creating layers of abstraction to more easily reason about a problem; other times, it’s the exact opposite, and you find yourself needing to go below the abstractions created. This may be necessary in order to fix a bug showing up in your program, to refactor parts of your program that need to change, or even to understand how the abstraction works at all and how it interacts with your program or other parts of the systems you’re constructing. Let’s start at a high level web app and work our way down to its guts.
When I first started learning Node 3 years ago, I had lots of recent experience with “paint by numbers” style MVC frameworks, like Ruby on Rails and ASP.NET MVC. Those are generally opinionated frameworks with lots of abstractions, but those abstractions help you get work done more quickly by reducing your cognitive load. Follow the instructions, put your models here, your controllers there, wire up your views this way and you’re set. I wanted something similar to build an app quickly in node.js so I read up on Locomotive. Locomotive is set up almost exactly like Rails so I took to it quickly. For simple user-facing webapps (say a CMS or an internal data reporting tool) it’s great. For something like a REST API, not so much. I started to dig and found that under the hood, Locomotive is just a configured Express with certain defaults chosen for you. It was great, but I wanted to learn more. Can I swap out the template library instead of using EJS? Can I muck about with the directory structure and change things to how I want them to work? It almost seemed easier to drop down to Express directly and configure things there. So that’s what I did next.
Express is the web library I have the most experience with on node. It has a lot of similarities to Sinatra, a simple Ruby web framework that is less opinionated/configured than Rails. Many things in Express are totally up to the app author, such as directory structure, plugins, and general architecture; Express is pretty unopinionated about most of these things, and just about any part of it can be configured or overridden with a plugin or module from npm. Express works well with plugins because they all follow a similar “middleware” API: namely, all middleware “modules” are exported as functions with the parameters (req, res, next) to work with incoming data and send out a response to the consumer. Even an Express app itself can be required/exported as a middleware module to be used in a parent app! If we take a look under the covers of Express, we actually find out that Connect is responsible for the middleware format/API. See a pattern yet?
Turtles all the way down
So what do I use and when? For something really simple with one endpoint I’ll happily call
http.createServer() with a single function to handle everything. For a user-facing web application (with or without API endpoints) I’ll use Express. For an app I’m developing with a team I’ll use Express with a middleware layer on top, like Kraken. It’s a flexible compromise between the “anything goes” mentality of http/Connect/Express and the paint by numbers mentality of Ruby on Rails. Finally, for a pure REST API application that won’t be serving anything but JSON and maybe XML, I choose Restify. It makes writing a consumable REST API super easy and comes configured to serve exactly that. You can make it work with static content, other node modules, or whatever, but it’s a pleasant experience for mid-tier/backend work.
I also stay on the lookout for better ways to do these things, and either incorporate them into my projects or switch to those new libraries/frameworks. After all, if I hadn’t been on the lookout for something new 3 years ago I never would have found node and all the cool libraries available to meet exactly your need. In short, take a look around, and don’t be afraid to look beyond the next level of abstraction, because you might really like what you find one or more levels down.