How to refactor your huge Node file

Your node module. (pixabay)

It all started out so simple. There was one file, a couple of CRUD functions, maybe a global connection object. Then the weeks went by and you added a few more functions. Then one day your coworker asks, “How did this get to be 12k lines of javascript?” Never fear, there are a few different ways you can handle this problem.

Option 1: Do nothing

Does it really matter? Surely you can just put it off until 20k lines? This is probably the most tempting of your options, and is actually a valid response for old, stable code that no one is going to have to look at. As a lot of wise(?) people have said, “Don’t fix what ain’t broke.” However, if you are ever going to have to add more functions (you know you will), then you should refactor the thing already.

Option 2: Split it up into multiple files

This is the easiest “do something” solution, but comes with it’s own set of problems. Your module (henceforth thing.js) file looks something like this, right?

// thing.js
var createObject = function(req, res) {
// ...
// ...
module.exports = {createObject, getObject, deleteObject, updateObject, permanentlyDeleteObject, banishObject, replaceObject, bossView, managerView, myView, anotherView}

So we’ve got a bunch of CRUD operations on Object and a set of pre-calculated views for the front end. Sounds like a good place to split the files right?

// thing.js
var views = require('./views.js')
var crud = require('./crud.js')
module.exports = {views, crud}

Uhoh, we just changed the contract for our module by nesting functions in other modules. Now everyone that uses our module needs to update their code!

// someone trying to use thing.js
var thing = require('./module.js')
// old way which is now broken
// new way I discover after it breaks

And what happens if we break up the crud module again? We’ll end up with an unmaintainable mess of a module tree. Enter Object.assign().

// thing.js
var views = require('./views.js')
var crud = require('./crud.js')
module.exports = Object.assign({}, views, crud)

Hooray! Now our service contract is the same as before, and anyone trying to use thing.js functions can call them the way they always have. Even better, if we split up crud.js or views.js, we can use the same Object.assign() trick to “flatten” those as well.

Here’s another trick. What if you want to add another function, but it doesn’t seem like it fits in views.js or crud.js? Try this:

// thing.js
var views = require('./views.js')
var crud = require('./crud.js')
var awesomeFunc = function() {
console.log('You're awesome!')
module.exports = Object.assign({ awesomeFunc }, views, crud)

Sweet! Now we can use all the functions in thing.js as well as views.js and crud.js as if they were all coming from a single giant thing.js, and each level of the tree can even reuse the functions “below” it.

// App.js that wants to use thing.js
var thing = require('./thing.js')

There are a few other ways you could do this, especially if your node module is built in an object oriented way (with classes or with function prototypes), but they are fairly complex and better explained by others. For further reading on that, check these out:

  1. — Ben Cherry offers a really excellent, in-depth guide to the module pattern which is probably overkill for most small applications.
  2. — Fearless_fool shows you how to pass the primary class definition into the submodule, and McMath teaches everyone the mixin pattern.