Organising Your Node Code into Separate Node Modules

Node.js code organization diagram

Note: This article provides practical guidance for structuring large-scale Node.js applications using modular architecture.

NodeJS and the JavaScript/NPM ecosystem have exploded in use over the past few years. The ability to rapidly develop features has made NodeJS a fantastic choice for early-stage startups and products. However, as the Node ecosystem has matured, increasingly complex and enterprise-grade software is being built around isomorphic JavaScript.

The Challenge of Modularization

A common hurdle you’ll have to cross is figuring out how to organize, structure, and “modularize” your codebase so it becomes easier to manage as it grows. This guide answers this question using Lerna, a multi-module package manager.

Writing an exportable NPM package is a separate lesson in itself and is not the scope of this guide. Please check out this guide for a simple explanation on how to do this.

Understanding How node_modules Work

Node.js modules structure

The typical naive approach to working with modules is already familiar to you. You just install a dependency using the npm install command:

npm install --save axios

What happens here is that the relevant code will be copied as a module into your node_modules folder. Now whenever you run node within this project, these packages are accessible via the “require syntax”.

However, when you want to break up your own codebase into modules, a few issues can crop up. The key issue being:

Do I need to publish changes to the npm registry each time I make a change to my submodules?

Local Modules Solution

NPM has provided a handy solution where you can easily install a local module using:

npm install --save file:../local-submodule-X

Specifying the module as file:../local-submodule-X is as simple as providing the relative path to the module with the “file:” prefix. This symlinks your local module to the current module’s node_modules folder. By definition, any changes made to your local submodule are automatically visible.

This solution should be sufficient to get you up and running with 2 or 3 modules, but it has some issues:

  1. Managing versioning of all modules
  2. Handling modules that require transpilations (JSX, TypeScript, ESNext Features)
  3. Managing the dependency graph across modules
  4. Running commands in all submodules when the number of modules grows

Lerna is a fantastic tool to answer all of these problems, which we’ll explore in another post. By implementing a modular architecture early in your project’s lifecycle, you’ll save countless hours of refactoring and maintenance as your application scales.