Implementing Onion architecture in NestJS
Tags: architecture, typescript, nestjs, webdev
Published: 2021-11-18 10:52:44

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjlw35okriyo96qu8uag.jpeg)

> _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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q0b8qyvzro3ykhz8bdc2.png)

- **Domain entities**: in the core of our application we have the domain, according to the Domain-driven design(DDD), we should focus our implementation around our Domain and all other layers are built around it.

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

```js

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

```

- **Repository**: is the layer that participates in the Domain Entity, like getting or deleting entity object but it has to abstract away database and infrastructure details, so it’ll work with any kind of database.

so let’s build **ArticleRepository**:
```typescript
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 <T> to have general Repository
```typescript
export interface IRepository<T> {
  get(id: number): Promise<T>;
  delete(id: number): Promise<void>;
  save(input: T): Promise<void>;
  update(input: T): Promise<T>;
}
```


- **Service**: here we implement the use cases, it participates in the Repository to get the data it needs

in our case we need a service to get an article by id and count article characters:
```typescript
export interface IArticleService {
  getArticle(id: number): Promise<IArticle>;
  getArticleLength(id: number): Promise<number>;
}
```

- **Controller**: for the sake of simplification I made the controller the first layer, hence NestJs is a server-side framework. But normally in literature first layer suppose to be the UI or test.

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.

```typescript
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.
**In the rest of the article, I’ll implement one of the Classes(ArticleService) and the rest will be the same.**

-----

### Implementing ArticleService
```typescript

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

  async getArticleLength(id: number) {
    const article = await this.repository.get(id);
    return article.body.length;
  }

  getArticle(id: number) {
    return this.repository.get(id);
  }
}
```
- must add @Injectable() so NestJs can inject it later in a class as dependency.
- the class `implements` the interface to make sure the class has the same functions
- as we see here ArticleService has dependencies like the Controller but this time its dependency is IRepository

-------

### 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**:
```typescript
@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:

```typescript

@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:
```typescript
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qki8oc7p1shf8dv69co.jpeg)

------

## Don’t be so radical about it:

In the end, Onion architecture 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!**