NestJs e Typeorm: Recuperando a entidade pelo parâmetro de rota
Tags: javascript, typescript, nestjs, tutorial
Published: 2022-05-21 19:23:42

Nestjs é um **framework progressivo** em **typescript** para criação de aplicações api e aplicações MVC.

Usando o sistema de rotas do nestjs é possível definir parâmetros na uri para serem usados pela sua aplicação. Esses parâmetros são recuperados como uma string, e a validação é normalmente executada em seu controllers ou serviços.

Para recuperar o parâmetro de rota, usamos o decorator **@Param()** que recebe dois (2) parâmetros, o nome do parâmetro e um transformer.

Os transformers são usados para tratar o dado que esta sendo recebido.  Os transformers usados para receber identificadores de entidade pela rota são:

- **ParseIntPipe** para identificadores numéricos
- **ParseUUIDPipe** para os sistemas que usam uuid

### Observação

Esse texto se destina a mostrar um recurso para serem usados em sistemas com NestJs com Typeorm, portanto, não vai ser mostrado como criar, instalar as libs relacionadas.

O Pipe criado abaixo usa o padrão ActiveRecord do Typeorm, no git do projeto tem uma versão com o padrão repository.

Obs.: Certifique-se ter instalado o cli do nestjs, caso não tenha veja na documentação de [Fist Steps do NestJs](https://docs.nestjs.com/first-steps#setup)

Obs.: É possível fazer com os **orm/libs** que o NestJs suporta para uso no banco de dados

### Criando o Pipe

Para começar precisamos criar um pipe. Vamos criar o pipe com o nome GetEntity,  é possível criar com o comando abaixo.

```bash
nest generate pipe GetEntity

# Ou

nest g pipe GetEntity
```

Esse comando vai criar dois arquivos:

- src/get-entity.pipe.ts: o próprio pipe com uma classe de nome GetEntityPipe
- src/get-entity.pipe.spec.ts: o arquivo de teste para o novo pipe

O conteúdo do **get-entity.pipe.ts** é:

```tsx
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class GetEntityPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
```

Irei renomear a classe gerada, removendo o sufixo **Pipe** e fica somente **GetEntity**.

### O construtor

Para começar vamos criar o construtor da classe e adicionar 3 (três) parâmetros.

- entity: A entidade
    - Não é uma instância da classe, é a classe pura
    - A classe precisa extender **BaseEntity**
- relations:  Um array com strings para carregar as relações da entidade, valor padrão [](array vazio)
    - O Typeorm só aceita as relações como array, por isso é necessário fazer essa conversão
- field: O campo que será buscado no banco, padrão ‘id’

O novo construtor ficara assim:

```tsx
import { BaseEntity } from 'typeorm';

export default class GetEntity implements PipeTransform {
  private readonly relations;

constructor(
    private readonly entity: typeof BaseEntity,
    relations?: string[] | string,
    private readonly field = 'id',
  ) {
    this.relations = typeof relations === 'string' ? [relations] : relations;
  }
//......
}
```

## O transformer

O método transformer vai receber o valor, validar e processar e retornar para o aplicativo.

Abaixo, o método transformer completo:

```tsx
async transform(value: any) {
  if (
    !value ||
    value === '' ||
    value === 'undefined' ||
    !Number.isInteger(value)
  ) {
    throw newHttpErrorByCode[HttpStatus.BAD_REQUEST](
      `No entity ${this.field} provided`,
    );
  }

  const entity = await this.entity.findOne({
    where: { [this.field]: value },
    relations: this.relations,
  });

  if (!entity) {
    const entity = `${this.entity.toString()}`
      .match(/\w+/g)[1]
      .replace('Entity', '');

    throw newHttpErrorByCode[HttpStatus.NOT_FOUND](
      `Validation failed for ${entity} value`,
    );
  }

  return entity;
}
```

### Explicando o Transformer

```tsx
if (!value || value === '' || value === 'undefined' || isNaN(value)) {
    throw newHttpErrorByCode[HttpStatus.BAD_REQUEST](
      `No entity ${this.field} provided`,
    );
  }
```

Validação para impedir que valores errados cheguem até a consulta do banco, como o valor do parâmetro sempre será uma string é necessário fazer essas validações, caso verdadeiro dispara uma exceção HTTP com o status de BadRequest

```tsx
const entity = await this.entity.findOne({
    where: { [this.field]: value },
    relations: this.relations,
  });
```

Faz consulta ao banco de dados com os valores recebidos.

```tsx
if (!entity) {
    const entity = `${this.entity.toString()}`
      .match(/\w+/g)[1]
      .replace('Entity', '');

    throw newHttpErrorByCode[HttpStatus.NOT_FOUND](
      `Validation failed for ${entity} value`,
    );
  }
```

Caso a entidade não exista, dispara uma exceção HTTP com o status de NotFound.

## Modo de uso

Para usar o novo **Pipe** criado, podemos criar ou utilizar uma rota existente, adicionar um novo parâmetro e utilizar o pipe. Veja alguns exemplos abaixo.

Recuperando a entidade

```tsx
@Get(':id')
findOne(@Param('id', new GetEntity(UserEntity)) user: UserEntity) {
  return user;
}
```

Recuperando a entidade com relações

```tsx
@Get(':id/posts')
findOneWithPosts(
  @Param('id', new GetEntity(UserEntity, ['posts'])) user: UserEntity,
) {
  return user.posts;
}

// Ou

@Get(':id/posts')
findOneWithPosts(
  @Param('id', new GetEntity(UserEntity, 'posts')) user: UserEntity,
) {
  return user.posts;
}
```

Recuperando várias entidades na mesma rota

```tsx
@Get(':id/posts/:postId')
findOnePost(
  @Param('id', new GetEntity(UserEntity)) user: UserEntity,
  @Param('postId', new GetEntity(PostEntity, 'user')) post: PostEntity,
) {
  return { post, user };
}
```

## Conclusão

Usando esse **Pipe** evita a repetição de código para recuperação do banco de dados, centraliza a validação.

Veja o conteúdo desse post no [Github](https://github.com/skay1994/nestjs-getentity-custom-pipe), no projeto você vai encontrar:

- Dois pipes, um com o modelo de repositório e com o modelo de ActiveRecord.
- Arquivo docker-compose com mysql
- Arquivo de exportação do Insomnia com as rotas do projeto

[Projeto GitHub](https://github.com/skay1994/nestjs-getentity-custom-pipe)