Cookie Consent by Free Privacy Policy Generator Aktuallisiere deine Cookie Einstellungen ๐Ÿ“Œ Introduction to the principles of clean architecture in a NodeJs API (Express)


๐Ÿ“š Introduction to the principles of clean architecture in a NodeJs API (Express)


๐Ÿ’ก Newskategorie: Programmierung
๐Ÿ”— Quelle: dev.to

This is the fourth article in our series on clean architecture (the previous ones are listed at the bottom of the page), and we're getting more technical with an introduction to implementing the principles in a Node.JS API with Express.

Let's take a look at the overall structure of a project and the details of the main concepts (entities, adapters, injection...).

Organization of main directories

Let's start by taking a look at the general directory structure. As is often the case, all our API source code is grouped together in the "/src" directory. Next, let's take a look at the Core vs. Infrastructure distinction:

Structure tree

The heart of the application in "/core"

This is where use cases, business entities and business rules reside. Not dependent on any external framework or library, it represents the bare minimum needed to run our application: it's the functional core of our application. It is itself divided into several sub-directories.

Use cases

In "/core/use-cases", we find the business use cases. Each use case has its own typescript file with, if required, its own input and output types (what goes into the use case and what comes out). There are two schools of thought:

  • A sub-folder with a grouping of use cases. Example: A user subfolder in which I group all user cases.
  • All use cases at the root of use-cases, with no particular tree structure.

I personally prefer the second solution, all "flat", for the simple reason that you never have to ask yourself the question "where do I put / where has this use case been put?". A simple example: do you put a user's address in "user" or "address"? There are as many answers as there are developers, so you might as well not put a sub-folder!

An example of a use case that allows you to obtain a book by its id:

class GetBook {
  private bookRepository: BookRepository;
  private logger: Logger;

  constructor() {
    this.bookRepository = container.resolve<BookRepository>('BookRepository');
    this.logger = container.resolve<Logger>('Logger');
  }
  async execute(id: string): Promise<Book | 'BOOK_NOT_FOUND'> {
    this.logger.debug('[Get-book usecase] Start');
    const data = await this.bookRepository.findById(id);
    return data ?? 'BOOK_NOT_FOUND';
  }
}
export default GetBook;

Entities

The "/core/entities" directory contains our business data models, such as "User" or "Product". These are simple objects that represent the concepts of our domain, and bring together their business rules.

Let's take the example of our "User", and more precisely of a user who is not logged in, and therefore not known to the API:

export class NotExistingUser extends User {
  constructor() {
    super();
  }

  public hashPassword(notHashedPassword: string) {
    const hmac = createHmac('sha512', this.config.salt);
    hmac.update(notHashedPassword);
    return hmac.digest('hex');
  }
}

Here, we have a class that extends User and thus retrieves its properties, and a method that belongs to it, that is, "its" business rule: You hash a password (to create your account or connect to it).

Ports

These are simple interfaces linking the core and the infrastructure. They serve as a contract without any concrete implementation. You may find ports for technical dependencies, such as a logger, or for repositories (databases, etc.). An example of a User :

interface UserRepository {
  findByEmail(email: string): Promise<User | null>
  create(user: User): Promise<void>
}

This contract includes two methods, each with input and output parameters. The code part therefore knows what it will receive from the infrastructure, and vice versa.

Technical details in "/infrastructure"

At the same level as core is the infrastructure folder, which manages all the technical details such as data persistence and calls to external services. This is where the interfaces defined in core are actually implemented.

The API

Let's start with the API, the folder in which we'll put Express' config, controllers and other middleware.

In detail, for controllers, the "/infrastructure/api/controllers" folder groups controllers by resource or use case.

Their role here is to retrieve data from the HTTP request, validate and map it to the inputs expected by the use case, execute the use case and finally format the response. We'll therefore find sub-folders for each resource, with a typescript file for the controller itself, the input and output DTOs, and the encoder/decoder for these inputs/outputs.

Adapters

As the name suggests, this directory contains the technical adapters for the ports defined on the core side. It's very important to identify implementation dependencies in the tree structure. Let's take our example of the User repository:

// /core/ports/user-repository.port.ts
interface UserRepository {
  findByEmail(email: string): Promise<User | null>
  save(user: User): Promise<void>
}

// /infrastructure/adapters/mongo/user.repository.ts
import { UserRepository } from '../../core/ports/user-repository.port.ts'
export class MongoUserRepository implements UserRepository {
  async findByEmail(email: string): Promise<User | null> {
  // logique d'accรจs MongoDB
  }
  async save(user: User): Promise<void> {
  // logique d'accรจs MongoDB
  }
}

At tree level, we find "adapters/mongo", which means that in this sub-directory we have all the technical implementation for accessing mongo DB. As you can see, the adapter takes over the contract specified in the port. If tomorrow I want to change my dependency to SQLite, for example, I'll simply create a second adapter, change the dependency injection, and there'll be no impact on the core.

Conclusion

This structure clearly distributes the various responsibilities of a Node.js API. The core has no external dependencies, ports are decoupled from business logic and implementation details are delegated to the infrastructure layer.

By following these Clean Architecture principles, your code gains in maintainability, testability and scalability. Although the example is given with TypeScript and Express code, this organization can easily be adapted to other Node.js frameworks or even any other language.
In the end, that's the whole point of clean architecture: no language or framework, but a return to basics, to common sense, and contrary to popular misconception, a return to simplicity!

Frequently Asked Questions:

  1. What are the advantages of following Clean Architecture principles in an Express project?
    The disadvantage of Express (or advantage, depending on your point of view!) is that it's an empty shell. So you can do absolutely anything you want, and also anything at all. Clean architecture provides a framework for the whole team.

  2. If I'm doing clean architecture, do I have to follow this structure to the letter?
    No, this structure is just one example. The most important thing is to respect the fundamental principles of Clean Architecture: separation of concerns, decoupling of technical details, external dependencies, etc.

  3. How are tests structured in this architecture?
    We haven't put it in this article, but use case unit tests can be put at the same level as each other, in /core/use-cases. Personally, I prefer to have a tests folder at the root, which is a convention we often see.

This article is an introduction to Clean Architecture and is part of a dedicated series on this topic. Stay tuned for more!

Want to learn how implement it with typescript and express? See my udemy course! In french or english ๐Ÿ˜‰

Articles on Clean Architecture:

...



๐Ÿ“Œ Introduction to the principles of clean architecture in a NodeJs API (Express)


๐Ÿ“ˆ 74.06 Punkte

๐Ÿ“Œ Clean Architecture Implementation in NodeJS


๐Ÿ“ˆ 34.92 Punkte

๐Ÿ“Œ Introduction to Clean Code Principles: A Deeper Dive with JavaScript Examples


๐Ÿ“ˆ 34.57 Punkte

๐Ÿ“Œ The difference between clean code and clean architecture?


๐Ÿ“ˆ 32.67 Punkte

๐Ÿ“Œ Build A News Website With NodeJS, Express, EJS and WP Rest API


๐Ÿ“ˆ 29.06 Punkte

๐Ÿ“Œ Deploying a NodeJSโ€Š-โ€ŠExpress API to AWSย Lambda


๐Ÿ“ˆ 29.06 Punkte

๐Ÿ“Œ Build a CRUD Rest API in JavaScript using Nodejs, Express, Postgres, Docker


๐Ÿ“ˆ 29.06 Punkte

๐Ÿ“Œ How to Create a CRUD API โ€“ NodeJS and Express Project for Beginners


๐Ÿ“ˆ 29.06 Punkte

๐Ÿ“Œ Data Protection Principles: The 7 Principles of GDPR Explained


๐Ÿ“ˆ 27.63 Punkte

๐Ÿ“Œ SOLID Principles / Open - closed principles -


๐Ÿ“ˆ 27.63 Punkte

๐Ÿ“Œ SOLID Principles Aren't Principles


๐Ÿ“ˆ 27.63 Punkte

๐Ÿ“Œ Data Protection Principles: The 7 Principles of GDPR Explained


๐Ÿ“ˆ 27.63 Punkte

๐Ÿ“Œ How do programming principles equate to life's principles?


๐Ÿ“ˆ 27.63 Punkte

๐Ÿ“Œ Path To A Clean(er) React Architecture - A Shared API Client


๐Ÿ“ˆ 27.22 Punkte

๐Ÿ“Œ Path To A Clean(er) React Architecture - API Layer & Data Transformations


๐Ÿ“ˆ 27.22 Punkte

๐Ÿ“Œ Aligning NodeJS with the Web: Should NodeJS Implement The Same APIs as the Web Browser?


๐Ÿ“ˆ 26.75 Punkte

๐Ÿ“Œ Mastering the Art of Clean Code: Unlocking the Power of Programming Principles


๐Ÿ“ˆ 24.94 Punkte

๐Ÿ“Œ Clean Code: Definition and Principlesโ€Š-โ€ŠPartย 1


๐Ÿ“ˆ 24.94 Punkte

๐Ÿ“Œ Clean Code: Definition and Principlesโ€Š-โ€ŠPartย 2


๐Ÿ“ˆ 24.94 Punkte

๐Ÿ“Œ Clean Code: Definition and Principlesโ€Š-โ€ŠPart 3 (Lastย Part)


๐Ÿ“ˆ 24.94 Punkte

๐Ÿ“Œ 5 Clean Code Principles in JavaScript


๐Ÿ“ˆ 24.94 Punkte

๐Ÿ“Œ Reverse engineering the NTK: towards first-principles architecture design


๐Ÿ“ˆ 24.24 Punkte

๐Ÿ“Œ Zero Trust Model of Information Security: Principles of Trust Architecture


๐Ÿ“ˆ 24.24 Punkte

๐Ÿ“Œ Application Architecture Design Principles


๐Ÿ“ˆ 24.24 Punkte

๐Ÿ“Œ Integration Architecture Guiding Principles, A Reference


๐Ÿ“ˆ 24.24 Punkte

๐Ÿ“Œ Taming Reactive NodeJS: Stream-oriented Architecture with Nest


๐Ÿ“ˆ 23.8 Punkte

๐Ÿ“Œ Introduction to MLOps Principles


๐Ÿ“ˆ 23.45 Punkte

๐Ÿ“Œ Dockerizing NodeJS, Express, and MongoDB App with NGINX as a Reverse Proxy


๐Ÿ“ˆ 23.38 Punkte

๐Ÿ“Œ Why should we use Typescript with NodeJs and Express?


๐Ÿ“ˆ 23.38 Punkte

๐Ÿ“Œ Flaw in popular NodeJS โ€˜express-fileuploadโ€™ module allows DoS attacks and code injection


๐Ÿ“ˆ 23.38 Punkte

๐Ÿ“Œ How to handle CORS issues when deploying a nodeJS express app on vercel??


๐Ÿ“ˆ 23.38 Punkte

๐Ÿ“Œ A Fast Introduction to Fastify (NodeJS Web Framework)


๐Ÿ“ˆ 23.01 Punkte

๐Ÿ“Œ 89-Nodejs Course 2023: Restful Routes: Introduction


๐Ÿ“ˆ 23.01 Punkte

๐Ÿ“Œ Rust GraphQL APIs for NodeJS Developers: Introduction


๐Ÿ“ˆ 23.01 Punkte

๐Ÿ“Œ Introduction to Nodejs


๐Ÿ“ˆ 23.01 Punkte











matomo