This content originally appeared on Bits and Pieces - Medium and was authored by Bruno Lombardi
You’ve been through this before: your application starts simple and easy to maintain. Then, it grows in complexity and the codebase starts to become chaotic! How can we avoid that?

In this article, we’ll explore the concept of a clean and adaptive Node.js architecture, discussing some principles of software engineering and design patterns to help you build scalable, maintainable, and easy-to-understand applications.
To make sure we learn how to use each principle in practice, let’s see a common software requirement for many applications: we are building a REST API, and we need to authenticate our users. Let's see some examples of how we can do that.
Separation of Concerns
Separation of concerns is a fundamental principle of software engineering, and it’s no different when building Node.js applications. Separating your application’s concerns into distinct modules makes your code easier to understand, test, and maintain. This can be achieved by dividing your application into three layers: the presentation layer, the business logic layer, and the data access layer.
Presentation layer
The presentation layer is responsible for handling user requests and returning responses. This layer should be as thin as possible, mainly serving as a mediator between the user and the business logic layer.
For example, I chose to write a controller to implement the presentation layer. The controller is responsible for handling a http request and returning a http response. The controller also sends the user input to our business logic layer. See it in practice in our current example:
Business logic layer
The business logic layer (BLL) is where most of the application’s code lives, and it’s responsible for implementing the core functionality of the application. It should be able to process data and apply rules and policies to ensure that the application behaves as expected.
To keep going with our Node.js example, we are implementing BLL in the form of services and controllers. In the example above in the presentation layer, the controller was forwarding the http request input to the AuthenticationService , let's see it's implementation:
We chose to implement our business logic in a modular and testable way. You can see that the AuthenticationService depends on other classes, and each of them is responsible for a single thing in our code. This way, we can build scalable, easy-to-understand applications. Also, you can write unit tests to each class and make sure they are behaving as expected.
Data access layer
The data access layer (DAL) is responsible for interacting with any external data resource, like a database, another API, or a messaging system, to mention some, but be sure there are many external resources an application depends on.
In our Node.js example, the DAL is usually implemented using data access objects (DAOs), repositories, or models. These components provide a standardized API for querying the database, and abstract away the details of the underlying storage mechanism. Let's see how we implemented the UserRepository class in our code:
In this example, we are using MongoDB as an external resource to query our data from, and yet another time we have a helper function, only this time it returns a document collection inside MongoDB.
This way, we can ensure that the application’s data storage and retrieval logic is decoupled from the presentation and business logic layers. And, if we need to switch the underlying database or external resource, we can do it following the same principle, without affecting other parts of the code.
A word on building scaleable, maintainable codebases
Maintaining a scalable and maintainable codebase is a top priority for any software developer. One tool that can help achieve this goal is Bit.
By utilizing modular components, Bit allows developers to easily reuse code across projects and reduce duplication, resulting in cleaner and more consistent code.
In addition to improving maintainability and scalability, Bit also facilitates collaboration among team members by enabling seamless component sharing.
Developers who prioritize efficiency and clean code will appreciate the benefits that Bit brings to the table.
Read more:
How to reuse React components across your projects
Plug it all together with design patterns
Software design patterns are reusable solutions to common problems that arise in software development. In the example we are studying, some problems start to arise. For example, you saw that we have multiple classes, each of them responsible for a single thing, and we need to wire them all together to make our application work correctly.
To solve our first problem, we need to create instances of each class and it's dependencies, then, we connect them to our framework of choice to handle HTTP requests, like express.js. We'll follow with our current example, and we'll discuss how to use the factory pattern to instantiate our classes and its dependencies.
After that, we'll create an adapter (another design pattern) for our controllers that connect them with express.js route handling.
Factory pattern
The factory pattern is a creational design pattern that provides a way to create objects without exposing the creation logic to the client. This pattern is used to create objects that share a common interface, but whose concrete classes are determined at runtime.
In our case, we need to create a LoginController instance, which we will then connect into express.js routing using an adapter. To create this instance, we also need to create it's dependencies. In the next example, we won't necessarily follow all the rules of the factory pattern. We are going to adapt it to our needs.
This is the Controller interface. Don't forget to make LoginController implement this interface. Then, we need a factory that creates an instance of this controller. To make our code simpler, we are going to create only the concrete LoginControllerFactory . Also, we are going to use a more functional approach, but of course we could make this as another class.
As you can see, we implemented our factory as function that returns a Controller . And even our factory can call other factories to get the dependencies needed. Another good thing here is, if at any moment we need a different dependency, like, if you want to use another type of encryption in the user service, you only have to change the corresponding factory of that dependency.
In factories you can also inject environment variables or configuration values, so provinding us another layer of abstraction.
Adapter pattern
The adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, allowing them to work together without modifying the source code.
The adapter pattern is used to adapt one interface to another, making it possible for classes to work together that could not otherwise because of incompatible interfaces.
We'll use this pattern to connect any Controller with an express route handler:
And now, here is how your final application would look like when you setup express.js:
Of course, there are more patterns and principles you can use when creating a Node.js application. But if you follow these simple principles, use separation of concerns keep your code easy to maintain and understand, and apply some design patterns when you face common problems, your codebase can grow healthy in complexity and scale.
And so can you!
Build apps with reusable components like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.
Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.
Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:
→ Micro-Frontends
→ Design System
→ Code-Sharing and reuse
→ Monorepo
Learn more
- How We Build Micro Frontends
- How we Build a Component Design System
- How to reuse React components across your projects
- 5 Ways to Build a React Monorepo
- How to Create a Composable React App with Bit
Clean and Adaptive Node.js Architecture with TypeScript was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Bruno Lombardi

Bruno Lombardi | Sciencx (2023-02-24T12:00:30+00:00) Clean and Adaptive Node.js Architecture with TypeScript. Retrieved from https://www.scien.cx/2023/02/24/clean-and-adaptive-node-js-architecture-with-typescript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.