Implementing Onion architecture in NestJS




What’s Onion architecture?

As shown in the picture, onion architecture is a way to structure the code by dividing it into domain-driven design layers. each layer can only access the layer below it throw its interfaces, and then using the dependency inversion principle each interface will be replaced with its class.

onion architecture

Onion Architecture leads to more maintainable applications since it emphasizes separation of concerns throughout the system.” Jeffery Palermo



Why apply Onion Architecture in NestJs projects?

NestJs is a service-side framework based on NodeJs. NestJs has many built-in features but most importantly for us now is the Dependency Injection and the possibility to add Dependency inversion which is what we need to apply the Onion Architecture.



Building simple server-side Blog:

In the rest of the article, I’ll try to explain the NestJs implementation using a simple blog project.

I’ll assume most of the article readers already know Nestjs so I’ll focus on the architecture in the code example.



Project layers in NestJs

project layers in nestjs

In our case, the Domain Entity is just the Article, so let’s do its interface:

export interface IArticle {
  id: number;
  title: "string;"
  body: string;
}

so let’s build ArticleRepository:

export interface IArticleRepository {
  get(id: number): Promise<IArticle>;
  delete(id: number): Promise<void>;
  save(input: IArticle): Promise<void>;
  update(input: IArticle): Promise<IArticle>;
}

As the Repository interface most likely will have the same functions for all Entities as it should mainly perform these abstract functions, I recommend using Generics that takes the Entity type as parameter to have general Repository

export interface IRepository<T> {
  get(id: number): Promise<T>;
  delete(id: number): Promise<void>;
  save(input: T): Promise<void>;
  update(input: T): Promise<T>;
}

in our case we need a service to get an article by id and count article characters:

export interface IArticleService {
  getArticle(id: number): Promise<IArticle>;
  getArticleLength(id: number): Promise<number>;
}

As Controller is our first layer and we won’t use it as dependencies somewhere else so no need to write an interface for it and we can implement it directly.

import { Controller, Get, Param } from '@nestjs/common';
import { IArticle } from './article.interface';
import { IArticleService } from './articleService.interface';

@Controller({ path: 'article' })
export class ArticleController {
  constructor(private readonly service: IArticleService) {}

  @Get(':id')
  async article(@Param() params): Promise<IArticle> {
    return this.service.getArticle(params.id);
  }
}



So are we done?

ofc not yet, we just wrote the interfaces but still didn’t write the actual implementation of them, and then replace the interface with the class in the run time.
I’ll implement one of the Classes(ArticleService) and the rest will be the same.



Implementing ArticleService


@Injectable()
export class ArticleService implements IArticleService {
  constructor(
private readonly repository: IRepository<IArticle>) {}

  getArticleLength(id: number) {
    return this.repository.get(id).then(
(article) => article.body.length);
  }

  getArticle(id: number) {
    return this.repository.get(id);
  }
}



Dependency inversion( replacing the Interface by the Class):

In the ArticleModule we can specify a string token for each class and use this token when we use the class as a dependency.

so let’s apply that with ArticleService, giving it a token in ArticleModule:

@Module({
  controllers: [ArticleController],
  providers: [
    {
      provide: 'ARTICLE_SERVICE_TOKEN',
      useClass: ArticleService,
    },
  ],
})
export class ArticleModule {}

and using this token in the Controller to get the Class in run it:


@Controller({ path: 'article' })
export class ArticleController {
  constructor(
    @Inject('ARTICLE_SERVICE_TOKEN')
    private readonly service: IArticleService,
  ) {}
  ...
}

Note: as strings are bound to errors, it’s best practice to assign the token string to a const variable and export it from the IArticleService file and then use it instead of the string directly:

export const ARTICLE_SERVICE_TOKEN = 'ARTICLE_SERVICE_TOKEN';

export interface IArticleService {
  getArticle(id: number): Promise<IArticle>;
  getArticleLength(id: number): Promise<number>;
}

— that’s it 🎉 now imagine if we needed to change any class, we’ll just add the new class to the useClass parameter in the Module without having to change the controller implementation.
depenciey inversion spongbob



Don’t be so radical about it:

In the end, Onion Archecturie was made to make the development process easier, so don’t try to force it everywhere where it does not make much sense due to some libraries limitation or other reasons.

Thanks for writing till the end and wish we meet in another article. take care!

Source: DEV Community

November 18, 2021
Category : News
Tags: Architecture | nestjs | onionarchitecture | typescript

Leave a Reply

Your email address will not be published. Required fields are marked *

Sitemap | Terms | Privacy | Cookies | Advertising

Senior Software Developer

Creator of @LzoMedia I am a backend software developer based in London who likes beautiful code and has an adherence to standards & love's open-source.