How to set a good project file structure
There isn't a formula to create a perfect project file structure but there are some "best practices" that can definitely help you make a good one.
A common problem among software developers is having to decide where to put the source code, images, libraries, documentation or anything else inside of a project directory. And though it's a common issue, it's also fairly easy to tackle by having in mind a set of "best practices" so that the project ends up happily living in its container folder and a developer can enjoy a comprehensible navigation within it.
⚠ Disclaimer: This article does not provide a CORRECT nor a SUBLIME way of doing things. Instead, it provides tips based on the writer's experience.
🌱 The root of the file tree
In order to end up with a usable project file structure, this article will guide you on the definition of one meant to be used by a react application. We'll begin with the very root of the directory assuming that an NPM package has already been initiated. It should look like this:
. <-- represents the directory root └── package.json <-- NPM package definition
It's so clean and perfect to this point but extra stuff needs to go inside of it. This root of the project's directory should be used to allocate files that might not be totally necessary for the final project's product but for the developer to know that to work with and tools to know how to act like. Here's a list of example files one can commonly find in this location:
- 📃 README - Documentation about the project itself. It may contain the purpose of the project, description of its functionality, documentation on build steps, etc.
- ⚖ LICENSE - Specifies how the project can be used or distributed along with its constraints to do so.
- 🙅♂️ IGNORE - This is commonly found when there's a version control system set on the project. If Git is being used, the file
.gitignorecould be included.
- ⚙ CONFIG - Depending on the project you're building, you might find tools configurations on the root directory, too. For example, if the project is a .net application, it's common to find an
.slnfile, if the project is a nodejs application using typescript, a bundler such as webpack and the documents get automatically formatted by Prettier, you may find files such as
- ♻ ENVIRONMENT - Some projects may rely on environment variables. Most of the times, these variables are defined in the root directory. For example, the Azure Functions Core Tools rely on a
local.settings.jsonfile for this purpose while NodeJS relies on
A root directory hosting some the aforementioned examples may look similar to this:
. ├── .env.development ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── tsconfig.json └── webpack.config.js
As you may notice, none of the files are really needed by a project's final product but they are important for a developer to work on the project. Any other files that have similar purposes should be stored on the root the project's directory to avoid losing track of them or get them mixed up with the project's actual source code.
🗃 Project sub-directories
It's common to find several sub-directories in a project, and each of them serves a different purpose. It totally depends on the kind of project you're building but usually one can find a structure similar to the following:
- ⚙ .DIRECTORIES - Many tools depend on more than a single configuration file and instead of storing a dot-file on the root of the project's directory, they may create a sub-directory with its name starting with a "." (dot) to store their configurations in a single place. Good examples you may be familiar with are VSCode and Git bucease if you're making use of them, you for sure can find the
.gitdirectories in your project.
- 🤹♂️ ASSETS - Contains files that may not need modifications and can be simply copied as part of the project's resulting output. Common files that are stored here are favicons, avatar pictures, type fonts, manifests, re-distributable scripts, etc. Common names used for this directory are
- 📚 DOCUMENTATION - You may know every bolt and nut holding your project together but when you plan to share it with other developers, it's a very good practice to let them know how the project works, looks, gets used and gets built via written documentation. Frequently, this directory is named
- 🍔 OUTPUT - The project gets compiled and its resulting product is usually put apart from the source code in a specific directory so that it can be easily grabbed. Usual names for this directory can be
bin. Also, it's customary to exclude this directory from source version control systems like Git.
- 👨💻 SOURCE CODE - Here's where the golden nuggets, the jewels, the juicy stuff gets stored at. More to come about this special directory below 😉. An habitual name for this directory is
- 🧪 TESTS - Another good practice among developers is to create and share automated tests for our project. This helps ensure that what the project is aimed to do is actually what it does. Most tests are written code, too. But mixing the tests code with the source code can crate confusion, so, it's better to separate them and give them their own place.
testis a very common name for this directory.
Here's an example of a project directory containing some of the sections mentioned above:
. ├── assets │ ├── avatar.jpg │ └── favicon.ico ├── docs │ ├── README.md │ ├── api-reference.md │ └── build.md ├── out ├── src │ ├── app.tsx │ ├── footer.tsx │ └── nav.tsx ├── test │ ├── app.test.js │ └── nav.test.js ├── .env.development ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── tsconfig.json └── webpack.config.js
👨💻 Source Code file structure
🥪 The layered approach
Not all applications will have the same structure, but they all can be divided into "concern layers." For example, when developing an API, you may want to include all
controllers into their own directory with a separate business logic within a
services directory and data definitions within a
models directory. This to aim for a better understanding of the project structure, scalability and ease of maintenance.
Another example is a desktop application wherein a
UI directory may hold all window designs but a
services directory would feature all business logic, a
models directory could hold all data definitions and a
data directory all database transactions logic.
With this approach in mind, we can elaborate our
src directory on our structure and add a
pages directory for entities that will make use of several modules at a time, a
layouts directory for pages "themes", a
modules directory where entities of the app are defined such as a header, welcome, posts list, footer, etc. All these make the
src directory look similar to this:
src ├── layouts ├── models ├── modules │ ├── footer │ ├── header │ └── post-list └── pages
💎 SOLID principles
These principles mainly apply to Object Oriented Programming but taking them as guidance will also give you a nice result with other methodologies.
- Single responsibility: A class should only have a single responsibility. That is, is should only affect a single specification of a program.
- Open-Closed principle: Software entities such as classes, modules, functions, etc. Should be open to be extended in functionality without having to be modified at their source code level.
- Liskov substitution principle: Taking
Sas a subtype of
T, objects of type
Tmay be replaced by objects of type
Swithout the program suffering a change on its desired properties.
- Interface segregation principle: Have you ever had a bad experience using a "universal" remote control? Well, the same thing happens with programming; a remote control may be thought like an interface for controlling a device such as a TV. The interface defines the actions that a recipient of it can do. This principle states that a recipient or client should not be forced to feature an action it's not going to use, therefore, it's always better to have several small interfaces rather than a big one.
- Dependency inversion principle: The summary of this principle is "High level objects should not depend on low level implementations." This is, any high-level entity in a program should be able to work under several scenarios and its functionality should be dependent of low-level implementations. For example, a "User Administration" module should be able to take care of its business without being dependent of a "User" class where features such as name, age, registration date or other data is defined. This same module should be able to leverage the task of saving data to a database by receiving the indication to do so, but the actual task should be delegated to a low-level class that takes care of saving the information.
More about this here
Knowing this, we can then think of adding files to the
src directory sub-paths like this:
src ├── layouts │ ├── blog-layout.tsx │ ├── common-layout.tsx │ └── home-layout.tsx ├── models │ ├── blog-post.ts │ └── navbar-item.ts ├── modules │ ├── footer │ │ ├── clickable-icon.tsx │ │ ├── external-links.tsx │ │ ├── index.tsx │ │ └── site-sections.tsx │ ├── header │ │ ├── index.tsx │ │ ├── logo.tsx │ │ ├── menu-button.tsx │ │ ├── nav-button.tsx │ │ └── navbar.tsx │ └── post-list │ ├── index.tsx │ ├── list.tsx │ └── post-card.tsx └── pages ├── about.tsx ├── blog.tsx ├── error404.tsx └── home.tsx
I hope you've enjoyed this article. Now, you have a couple of guidance points to follow when having to design a project's file structure.
Cover credits: Diagram made with diagrams.net | Banner vector created by pch.vector - www.freepik.com | FiraCode