In this article, I will share my opinion on how to structure your next Node.js project. In the past, I’ve worked on various projects both with Node.js and other technologies.
A well-structured project is crucial for the developer experience (DE), scalability and maintainability of the application, testing, and much more.
I won’t cover a specific framework because the shared structure can be applied no matter the tech stack.
After reading this article, you will learn:
- what’s MVC and technical responsibility separation
- technical vs. domain responsibility
- what’s modular monolith
- how to better start structuring and organizing your Node.js application
Disclaimer: Don’t take everything I share as an absolute. Software can be built in multiple ways.
What is Node.js?
There are many great articles, videos, and information on this topic, so that I won’t go into details. In a nutshell, Node.js is a runtime environment for using, writing, and executing JavaScript on the server-side, outside the browser. It’s great because you can use one language - JavaScript, both for the front-end and the back-end development.
The Old Way of a Node.js Project Structure
Maybe the most popular way of project structure is the so-called MVC pattern. MVC stays for Model-View-Controller. As the name says, you structure your application into 3 folders:
// ❌ MVC (technical responsibility)
/src
└───models
│ │ user.js
| | product.js
│ │ ...
└───controllers
│ │ user.js
| | ...
└───views
│ │ user.js
│ │ ...
└───...
Here you structure and separate your folders by their technical responsibility. Models’s user.js
is responsible for fetching data from the Database and/or some other ORM/DB stuff. Controller’s user.js
is responsible for handling HTTP requests, responses and/or business logic. Views’s user.js
is responsible for visualizing responses and data.
While being one of the most popular ways to structure an app, I haven’t seen the structure to scale well. After some time, it’s getting hard to navigate throughout the codebase and find a particular functionality. Looking into the different models don’t tell you anything about their relation and structure. Controllers invite developers to dump all the logic into controllers is a recipe for a disaster.
With this approach is hard to achieve a good elasticity and maintainability of the system.
There’s a better mental model to structure the application - by domain responsibility.
Technical vs. Domain Responsibilities
Another way to look at your application’s structure is by the domain perspective. Each folder represents a domain that’s part of the business. For example, user management, product management, etc.
This way, the structure of the applications resembles the way the business operates and its different domains/areas. It’s more intuitive because it’s closer to the real-world problems we solve as software developers.
💡 An application’s structure should tell you what it does and provide information about its domain.
An application’s structure in the financial and e-commerce sector should be different. They have different domains and operation, so this should be clear when looking into the codebase.
Start with the Modular Monolith
In the recent years, developers have favored the microservices architecture and while it provides benefits like better scalability, independence, etc., it comes with a cost - complexity. The other way of designing your application is the monolith one.
I would advise to better start with a modular monolith and while the application is evolving, improve the architecture if needed.
As the Gall’s Law states:
All complex systems that work evolved from simpler systems that worked. If you want to build a complex system that works, build a simpler system first, and then improve it over time.
You can easily migrate from a Modular Monolith Architecture to a Microservices Architecture later because each module/domain is separated and isolated. Basically, each module folder could go into a separate microservice very easily.
// ✅ Modular Monolith (domain responsibility)
/src
└───user
│ │ userQueries.js
| | userMutations.js
| | userServices.js
| | ...
│ │ index.js
└───product
│ │ productQueries.js
| | productMutations.js
| | productServices.js
| | ...
│ │ index.js
└───...
By embracing a Modular Monolith structure you get the scalability, isolation, organization of the microservices structure but without the complexity related to the Distributed Systems. This way you can iterate and move faster.
There’s a great article from Shopify Engineering Blog about Modular Monoliths and much more.
Conclusion
The better way to structure your Node.js application is by embracing the Modular Monolith architecture.
Modular monoliths are underrated.
Better start with a simple structure and improve later if needed.