DEV Community

Igor
Igor

Posted on

Applying Clean Architecture with Node.js in practice

In this article I will teach you how to develop a code structure that I use in most of my back-end projects and that fulfills its role of organizing and isolating the part that really matters, the business rule, very well.

At the end of this article you will have the basic knowledge to create a well-structured project in Node.js using Clean Architecture, but first I will briefly explain the concept of this software architecture. It is important that you understand that you do not need to be an expert on the subject to start applying it, not only in Node.js projects, but in any other project where you feel the need to have a robust architecture.

One of the biggest challenges is finding the right time to apply it, something that requires a lot of practice, as we don't always need to use it, and using it without prior planning can make a project very complex without need, it would be the same as using a bazooka to kill an ant.

A brief example would be a scenario where you are dealing with two projects: one of them is about a shopping checkout, something very crucial for your company, because after all, we are dealing with our customers' money and the company's cash flow. Now the second project is used to list products on a page, and that maybe has only two endpoints, one for listing the products on the web and another for a mobile application.

Now looking at these two scenarios, which project do you think needs to be much more structured and consequently have more robust code? Therefore, creating a much more elaborate code for checkout makes it easier to understand and easier to maintain in the future, as well as helping to avoid bugs and security flaws.

However, by applying this architecture, in addition to making the code structure robust, you also make it more complex, which can be a challenge for the rest of your development team, leading to difficulties in maintaining the code and consequently delays in delivery of demands.

I would recommend thinking twice before applying this architecture in the second scenario, as it may create unnecessary complexity for the project.

What is Clean Architecture?

Clean Architecture is a software architecture concept created by Robert C. Martin and promoted in his book Clean Architecture: A Craftsman’s Guide to Software Structure. There are other concepts and architectures, but this is the one I am most familiar with and have experience with. If you have already searched about Clean Architecture on the internet or read the book, you have already come across this image below, its main objective is simple: isolation of the business rule.

Clean Architecture Model

Quick explanation of the image concept:

  • The internal layers have no vision of the external layers, that is, the internal layers must not be aware of what is happening in the external layers
  • The two outer layers are where communications with the “external world” are located, such as configuring http routes, databases, gateways, etc.
  • The more internal a layer is, the more business rules it will have, that is, the use case and entity layers have the main business rules of the project and consequently should not be affected by the external layers.

Imagine that you are developing a project using Postgres as a database and for some reason your leadership decides to change the data to MongoDB, if you do not use Clean Architecture, you will have to change all the business rule code to adapt for the new database, which could cause a lot of rework and bugs, since with the clean architecture the business rule will remain intact, requiring only a change in database communication.

Let's code

Okay, but enough talking, let's get our hands on the code.

(note: I will not give installation commands or to start the Node.js project, as you can find these commands anywhere, or ask chat-gpt).

We will orient ourselves according to the clean architecture layers to create our code, but first we have to have the business rules.

For this example, we will set up something very simple like registering a product, following the following rules:

  1. You must register the product in the database
  2. You must send a registration confirmation email

Entity Layer

Let's create our first entity, the Product, we will use a class, having name, price and quantity

Entity

Use case layer

Now in our business rules application layer we have a class responsible for registering our product, in which we order the order in which each rule occurs. If in your development the use case starts to get too big I recommend using the ideas given in the Clean Code book where you should divide the class and other classes so that they only have one responsibility.

Use case

A very important point, note that in the class constructor we receive two parameters, which are typed with interfaces that I created myself, so when I instantiate the class I need to pass as parameters other classes that implement these interfaces, and it is precisely at this point that we apply the principle of dependency inversion described in SOLID.

An easy way to identify if your class does not comply with the dependency inversion principle and depends on third-party systems, just look at the imports within the document. If it is using a third-party library, be aware that your class has become dependent on that library.

Below are the two interfaces I developed, note the nomenclatures, Repository and Provider. We usually use the repository to integrate with data retention systems, that is, a database or something like that, while we use the provider for external functionalities.

Repository

Provider

Frameworks and drivers

Now we have the implementation of these interfaces using external services.

In the case of the email provider class, we use nodemailer and implement the IEmailProvider interface by placing the nodemailer rules to send an email.

IEmailprovider

In the repository class it follows the same idea as the provider, implementing the IProductRepository interface, however in this case we use typeorm to connect with a MySQL database and persist the product data.

IProducRepository

Adapter interface

Now we have reached the moment where everything connects, as so far we have created our isolated business rule in the use case and the implementation of our interfaces using third-party libraries.

Note that up until this point I have not commented anything about any framework for creating the REST Api, as it is not important for our business rule to know whether we use Nest.js or Express.js for example, as Uncle Bob would say, this is just a detail.

In the next examples I chose to use Express.js, so if you are going to use another framework, be aware that these examples will have to be adapted to your choice.

Next, we have our factory, where we create a class that extends the use case class and injects our implemented Repository and Provider, so if one day you need other libraries, you just need to redeploy the interfaces shown previously and make the change in the factory.

Factory

With the factory created, we can create our controller, which should only have the role of organizing the data that will be sent to the use case and also organizing the return data. Note that in this Controller class we have the create method, if you have a new use case, for example product update, just create a new method in this controller, like update for example.

Controller

Finally, we have the configuration of our route using express, note that we will have a /products route using the POST method and that calls our controller that starts everything.

Routes

Paths

Now just start the project, it will be running on port 3333 with the /products route ready to be called, and our REST API in Node.js with Clean Architecture is ready.

Top comments (0)