File and folder structure for Node-Express applications
I’ve seen as many code organization systems as there are applications. Everyone does it in their own way. Some are clearly over-complicated, others too simplistic to scale as development demands. And with their differences, they also have things in common.
Updated on September 2024
Without An Official Style Guide for Express, I will compile some good practices and tips for creating file and folder structures for Node-Express applications. Many of the ideas and advice are heavily inspired by Angular, which does have an official guide, and its LIFT principles, which is what I’m going to start with:
Locate: Group coherently
Identify: Name to indicate the content
Flat: Create subfolders if necessary
Try-DRY: Some redundancy can be beneficial
File naming convention.
Single Responsibility
By following good coding practices and clean architecture, you’ll likely have many classes or functional modules dedicated to very specific things. The idea is that each of those things ends up in a file. And that the file’s name describes what that thing does in the best possible way.
Indeed, this code responds to some business requirements (for example, reservations, payments…) or system requirements (for example, log, security…). Therefore, the file must contain, in fact, it should begin with, the name of that functionality.
Following any software architecture, concepts associated with it will soon appear, such as controller, service, repository… Or more general concepts such as utilities, class, model, etc…
Well, this is also important, so we already have the first rule:
The file name combines the functional and technical domains of a single element:
functional-name.technological-type.js|ts
Group in folders
Easy peasy if you only have a dozen of files. However, this is not the case for real-world solutions. We will face hundreds or maybe thousands of small files. How do we keep them under control?
You may expect me to enter the famous folder tree dichotomy: group by function or type. For sure, this is a legitimate question based on the two dimensions that we applied to name our files.
Let’s try to find a solution.
A universal base agreement
As I said, there is a pattern that, with slight variations, I find in almost all the Node-Express projects that I review. The basic functionality of Express is to build a web server, and its main contribution is the concept of middleware to process requests and issue responses.
On the other hand, sooner or later, you start to find common things you intend to reuse. And the need arises to have a mixed bag to store those things that don’t belong to anyone but that everyone wants.
So, it’s natural that a few folders appear for these features. And these are my proposals for top-level folders.
The first level folders should reflect that this is an Express server:
/api
/core
/shared
Flat or deep, a matter of size.
Now we know how to name the files and the three folders to save them. It’s time to determine whether this is enough or if we need more subfolders. From here on, there is no longer a fixed rule but rather something that depends on the number of files (size of the application) and the architectural style applied.
Complying with LIFT’s Flat principle, I will keep the folder structure as little nested as possible; some advice may arise from there.
Rule 5–15:
A folder with less than 5 elements does not need to be subdivided
A folder with more than 15 elements has to be subdivided
Folders between 6 and 14 items are divided if an obvious criterion arises
Let’s apply this tip to the main folders.
Following the REST methodology, we will find names of entities or resources (activities, reservations, users, payments, credentials…). Following a layered architecture, technological concepts appear (router, controller, service, and repository). The first gives us functional names, and the second gives us technical types, and with that, we can name files as we have seen previously, also complying with the LIFT principle of Identification.
If, at this point, your application does not contain 15 files… stop reading this article and dedicate yourself to more productive things or things that make you happier. No one worries about how to organize a dozen things.
The API is a navigation tree.
But the normal case is that you surpass it widely. So you will consider how to subdivide the API folder, and here, now, the famous question appears: divide by functionality or divide by type.
Both have advantages. Grouping by type allows you to establish a root for the three or four necessary folders (controllers, services…, etc.) that will be filled with the functionalities as they arise (reservations, users…, etc.).
But, my recommendation, and the most used, is that you choose a functional grouping based on the names of the resources exposed in the API. In this way, two advantages are achieved:
- Keep together the things that usually change together (functional changes affecting the router and the service…).
- Reflect in the folder system what the application does and not so much the technical details (Screaming architecture)
One more thing. Once you choose to group your API mainly by its functional endpoint criteria, nothing prevents you from grouping models or services at each level. Remember the 5–15 rule. Sometimes, a certain amount of technical grouping is necessary inside the functional one.
So, our rule for the API would look like this:
The API folder must reflect the route tree with the first-level end-points grouping the files by their business functionality. Then can be a models or services folder to help deal with big functionalites.
The core is a Middleware plain
A key success of Express is its ability to dynamically add functionality to the requests it processes. It is a brilliant application of the Chain of Responsibility behavior design pattern. These intermediate functions are known as middlewares, and you can create your own or adapt those offered by the framework itself or the community.
The important rule is that the code in the core folder is never imported by the API. The framework uses it once it is configured in your main app files.
Following the principle of single responsibility, I allocate a file to each functional need that the middleware resolves. In most applications, I don’t see more than a dozen of them, and I prefer to leave them in a flat structure. Only when there are really many do I decide to classify them into two or three folders that scream their original purpose: log
security
validation
…
The core middleware folder should be kept flat until the need arises due to obvious quantity and grouping criteria.
The Shared catch-all
Following clean code principles, we should keep our code DRY. That is, avoid duplication by sharing code. Many of these shared functions end up in middleware, but others are completely unrelated to the Express request-response processing pattern. They are simple utility functions, data model definitions, database commons…
Since this folder is prone to growing, and it is good that it does, sooner rather than later, it will far exceed the limit of 15 items and up to 10 times more. Your common sense and knowledge of the project will help you to categorize it appropriately. I can only remind you of the general principles:
The shared folder grows more than one level of nesting. Try to keep it flat, but feel free to group things for any criteria that make sense.
Summary
Here, you have an example with all those tips applied.
Sample code :
- LIFT principles: Locate, Identify, Flat, and Try-DRY. These principles help to organize the code coherently, clearly, and concisely.
- File naming convention: Use a combination of functional and technical domains to name the files, such as `functional-name.technological-type.js|ts`
- First-level folders: Three main folders to reflect the basic functionality of Express:
/api
/core/shared
- Flat over deep structure: Keep the folder structure as flat as possible, and only create subfolders when there are more than 15 files or when there is an obvious criterion.
Between 5 and 15 entries per folder.
The lowest level of nesting possible to facilitate localization.
Clear names that facilitate identification.
If you are an Angular developer, I have you covered with the Angular version of this guide: