javascriptnode.jsnestjs

How to properly handle errors in Nest.js service layer


We are building an API using Nest.js and MikroORM and we are having a debate on several things related to the service "layer" of Nest.js.

Let's suppose you have the following update method on a service.

This method does several things:

async update(
  id: number,
  updateProjectDto: UpdateProjectDto,
): Promise<Projects> {
  try {
    const project = await this.projectRepository.findOneOrFail(id);
    if (updateProjectDto.client_id) {
      project.client = await this.clientService.findOne(
        updateProjectDto.client_id,
      );
    }

    this.projectRepository.assign(project, updateProjectDto);
    await this.projectRepository.getEntityManager().flush();
    return project;
  } catch (error) {
    this.logger.error('Error updating project', error);
    throw error;
  }
}

Here's our findOne in clients

async findOne(id: number) {
  try {
    return await this.clientsRepository.findOneOrFail({ id });
  } catch (error) {
    this.logger.error(`Error finding client`, error);
    throw error;
  }
}

Our questions are the following:

Thanks in advance!


Solution

  • If you want to follow Domain Driven Design and Clean Architecture principles, then in your domain layer you should not throw exceptions because they belong to the interface (or api) layer.

    You can encapsulate the success or failure of the result using some libraries like Effect-TS or Oxyde.

    In this approach, the contract of the repository will be like this:

    export interface UserRepository {
      createUser(data: UserProps): Promise<Option<User>>;
    
      getUserByEmail(email: string): Promise<Option<User>>;
    
      getUserById(id: string): Promise<Option<User>>;
    
      checkActiveUserById(id: string): Promise<boolean>;
    
      getAllUsers<T extends PaginatedQueryParams>(
        params?: T,
      ): Promise<Collection<User>>;
    }

    And in the API controller you put the logic to return data or throw an exception:

      @PublicApi()
      @Post('/login')
      async login(@Body() body: LoginBody): Promise<JwtUser> {
        return this.jwtAuth.generateJwtUser(
          getOrThrowWith(
            await this.loginUseCase.execute(body),
            () => new UnauthorizedException('Login Error!'),
          ),
        );
      }

    If you want to deep dive into DDD and clean architecture i have created the following NestJS repository: https://github.com/andrea-acampora/nestjs-ddd-devops