Best file structure for an application

April 21, 2021

Have you ever wondered how the application structure may improve your work? Well, as an old programming saying goes, the code is written once but read many times, hence the effort you put into writing, or in this case, arranging your files, may be beneficial for future readers.
The file structure is an additional path to understanding the app, just like the index in a cookbook. If you do not prepare it properly, you may have a hard time grasping the whole picture while working on a task. The structure performs an even more significant role for programmers that are new to the codebase.

I’m a web developer, therefore I will write from my experience in web applications. Still, as I used different languages and frameworks, I hope my perspective and experience may be a universal trigger for a similar discussion regarding any language or environment you choose to work with.

You may use different approaches when it comes to arranging files in your application. Yet, whatever you decide, it is usually a matter of keeping close elements related to each other and separating those that do not have much in common. This simple rule allows programmers to remember and utilize the file structure to their benefit. I will describe the most common divisions I’ve encountered in recent years and look at the pros and cons.

Division by type domain

One of the most apparent file structures, especially for small applications, is dividing your files by the type of logic they implement. For the sake of an argument, let’s say we consider three types of files that represent visual components, user actions, and state management.
When you keep a single implementation in a single file, you can easily place proper files in directories matching the type.

This approach is straightforward to maintain. It is also deterministic because you would never need to move your files around, as the type of logic they implement is determined from the very beginning and does not change over time.

The downsides of this solution start to kick in when your application grows in size. Files related to each other by business logic are scattered across the file structure, hence a complete change of a single mechanism involves modifying files very distant from each other. Besides, when you decide to be strict about the separation by type, you should keep separate directories for rare or unique files, just to be consistent, and that can bring some overhead from the very start.

All in all, this strategy is not wrong when you work with a small project or a short-lived one, but when the application grows in features and size, you will experience headaches jumping around all over the repository.

Division by visual domain

The second possibility is to divide files by visual domain. Quite often, it is represented by dividing logic in views at the high level of the application. Nevertheless, this approach may also be used down the directory tree when implementing a low-level component with many unique dependencies. In this method, you organize your files into directories by the visual feature they implement.

This strategy seems quite natural and easy to understand. It allows for quick navigation between files because those visually related to each other are very close. It is suitable for apps with unique views and not too much of a shared logic.

The most important concern with this approach is that it is not deterministic compared to the previous one. You may find it challenging to determine where some low-level component should end up because that requires answering the following questions. What happens when you use the same component in two different parent components? What happens when you stop using the given component in one of the parent components? Where do you place your code, and will you move it in the future? Well, the very idea that the usage of a certain logic determines the place where it resides is risky, and you should be very careful choosing this method at the high level of your application.

Of course, there are some excellent examples where that division fits perfectly, but it usually requires particular apps with distinct views or at least parts of the app where you can use this strategy at a local level.

Division by data domain

The last possibility I would like to present is dividing files and logic by the data it interacts with. Let’s say you have some visual components, corresponding actions, and utilities. Despite implementing different aspects of the application flow, all those files should be close to each other when they operate on the same data, same model if you prefer. It is common and natural to import a logic between those domains, but the data type determines the files’ placement.
The idea is to divide your source code into data domain directories. A new one appears when your application incorporates a new data model, and every new feature operating on current data is placed in currently existing directories.

That kind of division is deterministic, even though sometimes you may find it challenging to decide where to put some files. Most of them operate on well-defined data types and do not change their residence over time. This approach is scalable and allows you to easily navigate files because when you understand their nature, you will know where to find them. Besides looking at potential assignments, the single task usually relates to specific data, which induces changes to every corresponding code, like state management, user actions, and visual representation. The same data invariably connect all those places, so when you keep all those files close together, it helps you a lot.

The downside of this strategy is that it is not very straightforward at times. Sometimes, it is not apparent where to keep some logic, and you may need to create a data domain that is a unique combination of other data domains already in place. A perfect example is a dashboard view, which usually presents different data types, and you should not try to fit it into already existing domain directories. Instead, you should create a separate one — a dashboard data domain. Those situations may bring some overhead, and small applications may not get good value for money when it comes to using this approach.

On the whole, it is usually the best method for apps from moderate to big ones. Using this at the top level of your source directory brings data-related files closer and does not require moving them around when the business logic changes.

How to structure files?

As you probably expect, there is no silver bullet to answer this question. I believe that experience with different possibilities and a complete understanding of various ideas bring you closer to making responsible decisions and adjusting your file structure to your needs.

The final solution is usually somewhere in the middle. You may choose one file structure at the top level of your app and prefer a different one lower inside already specified domains. Whatever works and suits your team’s purpose is the best option, as long as the decision is a mindful one.
To give an example, I like to divide by data domain at a high level, so that I see what distinct categories my app operates on and navigate easily between them. Lower in the file structure, I prefer to divide files by type, because at this level, they usually strongly correspond to each other, and I need some kind of deterministic division. At the bottom, when I encounter more extensive visual components, I keep them in one place because they are encapsulated and not interconnected to other parts of the app.

The best lesson to remember is understanding the advantages and disadvantages of specific approaches and then structuring your code mindfully utilizing this knowledge.
I hope this leaves you with something to think about. Cheers!