This article was originally published at https://www.blog.duomly.com/how-to-create-frontend-project-structure-that-scales-and-is-easy-to-maintain/
The frontend has never been so complex as it is today. A few years ago, it was just about some colorful elements placed in the table, and simple events to send the data to the backend.
Since modern frontend frameworks and libraries came into play, the client-side became responsible for a big part of logic and functionality. Now, we even start building serverless projects, where almost everything needs to be processed by the frontend.
This approach requires us to carefully plan the architecture of the frontend application and organize the project in the best possible way to make it scalable and easily maintainable.
When we start a new application, that may grow in time, it’s not enough to set up a clean installation of the app using CLI and just start coding. When the project grows, it can become really difficult to add new features or maintain the older code. That’s why it’s necessary to think about the proper organization of the folders structure, make it easy to use, and save time on the onboarding process for new programmers.
Before organizing the app’s folders structure, it’s important to understand the flow of the application, we will be building and have the technology selected.
In this article, I’d like to focus on an application built with React.js because this frontend stack it’s a choice of many companies, and it brings a lot of difficulty for developers.
React.js doesn’t provide a lot of insight about structuring the code base, as for example Angular. Besides that, installing additional libraries and plugins necessary for the React.js application like Redux or React-Router requires the initial structure to be changed for the better maintainability and scalability. And as always, it’s better to do it at the beginning of the development, then try to change it when the deadline is close.
To plan and organize the maintainable and scalable frontend application, we need to understand how it works and how the elements interact with each other. But first, let me explain what it actually means that the application is scalable and maintainable.
If the application is scalable and maintainable, developers can create new features quickly and make changes in the existing ones without interference in the whole application.
In the image, I illustrated what’s happened in React.js application with Redux and React-Router in the background. As you can see, when the user interacts with the application, the router shows the user a particular view with the components. Each view can have more than one component.
Next, the particular component connects with the redux if it needs to interact with any data. So, let’s go a little bit deeper about what’s happening in the Redux.
When the action is called, then there are two options. It may call the API or not. Action is dispatch, and the data is sent to the store of the application. When the change happens in the store, it emits the change event, and events and components get the updates.
If we know how it works, we can take about patterns used in the development, and then I will tell you about three approaches of folder structures in React.js applications.
We can use the flat structure of the folders in the frontend application. This is one of the most common structures, wherein the root folder of our application we separate the logic from the views and redux related files.
Let's take the example application, which will be a blogging platform, and let's see how the folders structure should look in the flat structure.
└── src ├── api │ ├── api.js │ ├── posts.js │ ├── comments.js ├── components │ ├── PostComponent.js │ ├── CommentComponent.js ├── shared │ ├── ButtonComponent.js │ ├── ModalComponent.js ├── containers │ ├── PostListContainer.js │ ├── CommentListContainer.js |── actions │ ├── PostActions.js │ ├── CommentActions.js ├── reducers │ ├── PostReducers.js │ ├── CommentReducers.js |── App.js |── store.js
Let's go through the folders we have in the app root file in a flat structure example.
The first folder is api, where we create a code responsible for the application's API calls. Inside the folder, we divided the code into files for the specific feature.
The next folder is components folder, where we place presentational components, without logic, in our case, it's PostComponent and CommentComponent.
As in every application, there are some common components used in the whole application, the shared folder comes useful. Here we can place all reusable components like Buttons.
Then there's the containers folder. Containers in React.js are the components that can consist of other components and the logic. In our example, the PostListContainer will have the PostComponent inside.
Next, we have two folders from Redux, actions, and reducers, and in each of the folders, we place action and reducer files according to the functionality.
The App.js and store.js files are placed in the root file of our application.
Now, let's take a look at the advantages of the flat structure of the project:
- easily understandable code structure, which helps in the easier onboarding process of new developers;
- easier and faster development without an advanced structure;
The flat structure of the files in React.js also has some disadvantages:
- there's no separated folder for the whole Redux file, they seem to be everywhere in the application;
- if the project grows, it can be difficult to find a certain file, in case of debuting or maintaining the older code;
- when adding the new feature, we need to change a lot of folders, and we have to work on the whole root folder;
Let's summarize. If the application is not large and not going to grow a lot, this solution can work pretty well.
Another factor that should be considered when selecting the flat structure should be if the application has more view and logic components or a bigger state. If the application consists of view and login mostly, then the flat structure will be very easy to maintain, but in case of a bigger state, the reorganization may be necessary and creating some more Redux friendly structure of the code.
To explain to you what is Domain Driven Design, first, I need to explain to you what's domain means in the software development context. Domain refers to the "sphere of knowledge and activity around which the application logic revolves." We can say that the domain, in this case, is business logic.
Let's go deeper into Domain-Driven Design in frontend development. To manage the complexity of the application with Domain-Driven Design, we have to place our domain's model in the context.
To start organizing our application with Domain-Driven Design principles, we have to organize our domains. It's worth remembering that there isn't one way to do it. Let's take a blog platform as an example again, and in the traditional implementation, the structure of the folders would look like this.
└── src ├── redux │ ├── store.js │ ├── actions │ │ ├── PostActions.js │ │ ├── CommentActions.js │ ├── reducers │ │ ├── PostReducer.js │ │ ├── CommentReducer.js │ │ ├── index.js ├── components │ ├── PostComponent.js │ ├── PostsListComponent.js │ ├── CommentComponent.js │ ├── CommentsListComponent.js ├── containers │ ├── PostContainer.js │ ├── CommentContainer.js |── App.js
If we'd try to change the structure of the application folders with Domain-Driven Design, we would need to distinguish domains, and in this case, I'd divide it for App, Post, Comment.
In this case, the folders' structure for our React.js application would look a little bit differently.
└── src ├── app │ ├── App.js │ ├── reducers.js ├── post │ ├── PostComponent.js │ ├── PostContainer.js │ ├── PostReducer.js │ ├── PostActions.js │ ├── PostsListComponent.js ├── comment │ ├── CommentComponent.js │ ├── CommentContainer.js │ ├── CommentReducer.js │ ├── CommentActions.js │ ├── CommentsListComponent.js
As you can see in the example above, it's clearly visible now what kind of domains we have in the entire application, and in this case, adding new domains is adding a new directory.
It's very convenient because everything, even the redux files, is placed as a part of the specific domain folder.
Each application has some common components like buttons, popups, etc., and that's why we could also think about the folder for those. In my opinion, it's the best option to add the shared folder in the App domain folder like below.
└── src ├── app │ ├── App.js │ ├── reducers.js │ ├── shared │ │ ├── ButtonComponent.js │ │ ├── ModalComponent.js ├── post │ ├── PostComponent.js │ ├── PostContainer.js │ ├── PostReducer.js │ ├── PostActions.js │ ├── PostsListComponent.js ├── comment │ ├── CommentComponent.js │ ├── CommentContainer.js │ ├── CommentReducer.js │ ├── CommentActions.js │ ├── CommentsListComponent.js
Another question that needs to be answered in this case is about the tests. Where should we place the test files in this kind of folder structure?
There are two options, one is to place the test files in the main domain folders, or the other way would be to create a separate folder for the tests. I would go for the first option, as in my opinion, it's easier to find the files.
This approach is also known as a feature-based structure.
Let's take a look at the pros of using Domain-Driven Design in the folder structure of the frontend project.
First of all, it's an easy pattern for development to divide the application according to the easy rule of domains.
Domain-Driven Design approach in the structure of our frontend application makes it easily scalable, readable, and maintainable.
It's also easier to bring new developers to the project, as the structure is clear and understandable.
Features of the application are totally separated, so if developers have to fix bugs in one part of the application, they don't mess in every folder.
Even Redux files are part of the domain folders, so if application grows, there won't bee any mess in the state;
Besides all the great things that DDD brings to the project, there are also some disadvantages, that would be good to mention:
It may take some more time to understand the main domain in the application's business logic.
Domain-Driven Design is better for the bigger applications, where we can find at least three or four domains.
This approach is one of the most functional in React.js applications development. It works well in large and small applications, no matter is they are more focused on the view and logic or the state.
It's also a great solution when the future of the application is not clear because when there will be a need to make changes or add new features, that will be fast and easy. The only bigger issue with this approach I can see is the time that needs to be spent on preparing the proper architecture.
The last approach I’d like to tell about here is splitting view and state. It’s similar to the flat structure, but it has an improvement. All the redux folders and files are gathers in one directory, and the folders responsible for the view and logic stay as they were in the root folder.
This approach is a much better structure for the applications which have a bigger state, and need to keep lots of Redux related files. Let’s take a look at the example with our blog platform and view-state split structure.
└── src ├── api │ ├── api.js │ ├── posts.js │ ├── comments.js ├── components │ ├── PostComponent.js │ ├── CommentComponent.js ├── shared │ ├── ButtonComponent.js │ ├── ModalComponent.js ├── containers │ ├── PostListContainer.js │ ├── CommentListContainer.js |── redux | |── store.js | |── middleware.js │ ├── post │ │ ├── PostActions.js │ │ ├── PostReducers.js │ ├── comment │ │ ├── CommentActions.js │ │ ├── CommentReducers.js |── App.js
In the example above, you can see that it’s similar to the flat approach, but actions and reducers folders were moved removed, and the files were moved to the redux folder. Inside the new folder, actions and reducers are grouped by the feature.
Let’s go through the advantages of using the view-state split structure of the folders in React.js application.
All the Redux code is put in one place, so the refactoring is pretty easy, especially when files are grouped by the feature inside the redux folder;
It’s easy to find a certain part of the state;
Maintaining the application is not complicated, and also creating new features is simple, because it’s enough to add one folder for all the redux functionality;
Let’s got through the disadvantages of this approach.
If the application is large with a big view and logic layer and also a big state, it can become difficult to maintain, because of the necessity of finding the state corresponding to the particular view;
It can be difficult to bring new developers into the coding in a short time, as the structure can be difficult to understand.
No specific location for the logic in the structure;
It’s time to summarize what kind of application it should be used. In most cases, the view-state split approach will be a good solution for most medium-sized applications that are not going to grow a lot. If the team of developers working on the project is big, there may be some blocking issues, and in this case, the feature-focused structure will be a better solution.
In this article, I started with how the application flow works from the background. Then, I introduced you to three different methods of structuring the frontend project. I also summarized in what kind of project you should use a particular structure.
From the analysis above, we can assume that the best possible approach we can select if the application can grow and we would like it to be scalable and maintainable without reorganizing it later, would be the Domain-Driven Design approach, differently called feature-focused structure.
Of course, if you are sure that the project won’t be much more significant, it’s worth considering a flat structure for the small project with view and logic focus or the view-state split structure for the medium-sized applications.
I hope you’ll find this article useful, especially since it’s not straightforward how we should organize front-end applications, primarily when we use React.js as a primary project technology.
Thank you for reading,
Anna from Duomly