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
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.
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 é:
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
- 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:
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:
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
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
const entity = await this.entity.findOne({
where: { [this.field]: value },
relations: this.relations,
});
Faz consulta ao banco de dados com os valores recebidos.
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
@Get(':id')
findOne(@Param('id', new GetEntity(UserEntity)) user: UserEntity) {
return user;
}
Recuperando a entidade com relações
@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
@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, 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
Top comments (0)