Skip to content
/ bp-nestjs Public template

Full back-end boilerplate based on NestJS and MikroORM. Written in TypeScript.

License

Notifications You must be signed in to change notification settings

etienne-bechara/bp-nestjs

Repository files navigation

NestJS Boilerplate

⚠️ Atenção!

Este repositório foi dividido em vários pacotes menores e não será mais atualizado, confira os componentes em:


Boilerplate baseado em NestJS com intuito de prover iniciação rápida de projetos em Node.js.

Guia Rápido

1. Clone este repositório renomeando-o de acordo com seu novo projeto:

git clone https://github.com/etienne-bechara/bp-nestjs.git meu-novo-projeto
cd meu-novo-projeto

2. Execute o script de setup, que irá instalar as dependências e renomear o remote origin para boilerplate:

npm run boilerplate:setup

2a. Futuramente, caso deseje sincronizar as atualizações deste boilerplate, execute:

npm run boilerplate:udpate

2b. Caso não queira utilizar algum dos serviços opcionais, desinstale-os via:

npm run uninstall:orm
npm run uninstall:redis
npm run uninstall:sentry

3. Suba a aplicação localmente através de:

npm start

Índice


Utilização

Após seguir os passos do Guia Rápido acima, envie uma requisição GET para localhost:8080.

O retorno a seguir indica que a aplicação subiu com sucesso:

{
    "error": 404,
    "message": "Cannot GET /",
    "details": {}
}

Boilerplate

Sempre que quiser atualizar o boilerplate a última versão, execute:

npm run boilerplate:update

Caso tenha alterado arquivos na raiz ou no diretório /source/core provavelmente será necessário resolver conflitos de merge antes que possa commitar as alterações.

Depuração

Por padrão, a aplicação irá expor um sessão de debug na porta 9229.

Ao utilizar a ferramenta VSCode como IDE de desevolvimento, basta executar npm start e pressionar F5 para conectar o debugger.

Você pode criar os breakpoint diretamente no arquivo .ts que eles serão automaticamente mapeados pelos .js em execução.

Dependências

Todas os pacotes que este projeto utiliza estão configurados com a versão exata.

Para realizar atualização dos mesmos, execute um dos scripts a seguir de acordo com o nível de versão que deseja subir.

Siga o padrão Semantic Versioning: {major}.{minor}.{patch}

npm run update:patch
npm run update:minor
npm run update:major

Componentes

Vários dos frameworks que este projeto é composto são opcionais e podem ser facilmente excluídos para economia de memória RAM durante execução.

Frameworks

Documentação Reponsabilidades Observação
NestJS • Injeção de Dependências
• Inicialização do Servidor (Express)
• Middlewares e Fluxos de Validação
• Filtro Global de Exceções
Irá carregar automaticamente todos os arquivos nomeados como *.module.ts.
Jest • Testes Unitários
• Testes E2E
Instalado apenas em ambiente de desenvolvimento.
Crie os arquivos de teste no padrão *.spec.ts.
Sentry • Monitoramento em Tempo Real
• Rastreio de Exceções
Opcional
Habilite configurando a variável SENTRY_DSN no .env.
MikroORM • Abstração de Banco de Dados como Entidades
• Geração e Execução de Migrations
Opcional
Habilite configurando as variáveis ORM_* no .env.
Redis • Armazenamento de Dados do Tipo Chave/Valor
• Compartilhamento de Alta Performance em Serviços Distribuídos
Opcional
Habilite configurando as variáveis REDIS_* no .env.

Utilitários

Documentação Reponsabilidades Utilização
Axios • Requisições HTTP(s) externas Foi criado um wrapper em torno da bilblioteca para padronização de exceções.
Injete o serviço HttpsService na classe que deseja utilizar.
Class Validator • Decorators para Validação de Objetos Dentro da classe, antes da propriedade, utilize um ou mais dos decorators do pacote.
No caso dos controllers a validação é aplicada automaticamente.
Class Transformer • Conversão de Objetos para Classes
• Conversão de Tipo de Propriedades
Utilize um dos métodos do pacote em conjunto com os decorators fornecidos.
Em geral, não será necessário ao menos que altere algo a nível de boilerplate.
Moment • Parsing e Formatação de Datas Inicialize através de moment() ou moment(stringDate), e siga os métodos conforme documentação.

Domain

É o conjunto de todos os componentes que definem um módulo do projeto.

Por exemplo, os usuários, as empresas, a autenticação, a API XPTO externa, etc.

O domínio é representado por uma pasta dentro do diretório /source.

Os inclusos no boilerplate estão dentro de /source/core para melhor organização.

Config

Cada domínio, pode ter uma grupo de configurações definidas em um arquivo *.config.ts.

Ao criar um serviço que extenda a class AppProvider (detalhes adiante), é possível obter as recém criadas configurações através do método this.getConfig().

Exemplo:

export class MailerService {
  private config: MailerConfig = this.getConfig();
}

As configurações são divididas em duas categorias:

Variáveis de Ambiente

  • Possuem informações sensíveis ou que variam de accordo com o ambiente.
  • São declaradas em modelo chave/valor dentro do arquivo .env na raiz do projeto.
  • Todas são inicializadas como string.
  • Utilize o decorator @Transform() da lib class-transformer para convertê-las de tipo.
  • Devem ser declaradas sem valor padrão.

Exemplo:

export class AppConfig {

  @IsIn(['DEVELOPMENT', 'STAGING', 'PRODUCTION'])
  public NODE_ENV: 'DEVELOPMENT' | 'STAGING' | 'PRODUCTION';

  @Transform((v) => parseInt(v))
  @IsNumber()
  public PORT: number;

  @IsOptional()
  @IsString() @IsNotEmpty()
  public APP_AUTHORIZATION: string;

}

Opções do Serviço

  • Não possuem informações sensíveis e são idênticas em qualquer ambiente.
  • São declaradas dentro do próprio arquivo *.config.ts.
  • Devem ser inicializadas com valor padrão.

Exemplo:

export class AppConfig {

  public APP_TIMEOUT: number = 2 * 60 * 1000;

  public APP_CORS_OPTIONS: CorsOptions | boolean = {
    origin: '*',
    methods: 'GET, POST, PUT, DELETE',
    allowedHeaders: 'Content-Type, Accept',
  };

  public APP_VALIDATION_RULES: ValidationPipeOptions = {
    whitelist: true,
    forbidNonWhitelisted: true,
  };

}

Entity

Conceito aplicável apenas se utilizado o ORM

Define uma entidade da modelagem de dados, em um banco relacional, podemos considerar como uma tabela.

São definidas pelos arquivos *.entity.ts utilizando decorators para configurar tipo das propriedades e relacionamentos.

Refira-se a Mikro ORM - Decorators Reference para todas as opções.

Predefinidos

Classe Arquivo Descrição
OrmIdEntity ormid.entity.ts Extenda essa classe para já incluir uma coluna UUID primária.
OrmTimestampEntity ormtimestamp.entity.ts Extenda essa classe para já incluir colunas UUID primária, data de criação e data de atualização.

Exemplos

@Entity({ collection: 'user' })
export class UserEntity { 
// utilize extends OrmTimestampEntity acima para simplificar

  @PrimaryKey()
  public id: string = v4();

  @Property()
  @Unique()
  public name!: string;

  @ManyToOne()
  public team!: TeamEntity;

  @Index()
  @Property({ columnType: 'timestamp', onUpdate: () => new Date() })
  public updated: Date = new Date();

  @Index()
  @Property({ columnType: 'timestamp' })
  public created: Date = new Date();

}

Modules

Unifica todos os componentes de um domínio em um arquivo *.module.ts que será lido e carregado pela aplicação.

Novas classes de módulos devem ser decoradas com @Module() e serão carregadas automaticamente quando o serviço iniciar.

A criação de qualquer componente descrito a seguir, sem integrá-lo em um módulo, implicará no mesmo não ter efeito algum.

Refira-se a Nest JS - Modules para mais informações.

Predefinidos

Classe Arquivo Descrição
AppModule app.module.ts Ponto de entrada da aplicação, carrega todos os outros módulos.
HttpsModule https.module.ts Carrega o serviço abstraído de requisições HTTP(s) baseado em Axios. opcional de envio de e-mails via SMTP.
OrmModule orm.module.ts Carrega a camada opcional de abstração para armazenamento em banco de dados.
RedisModule redis.module.ts Carrega o serviço opcional para armazenamento chave/valor de alta performance.

Exemplos

@Module({
  controllers: [ UserController ],
  providers: [ UserService ],
  exports: [ UserService ],
})
export class UserModule { }

Controllers

São responsáveis por receber as requisições HTTP, aplicar validações, e redirecionar os dados para o respectivo serviço de manipulação.

Os controllers seguem o padrão *.controller.ts, e devem ser criados dentro da pasta de seu respectivo domínio e importados na propriedade controllers do módulo correspondente.

Para definir um controller utilize o decorator @Controller('domain_name').

Sendo que domain_name será a rota base ao executar requisições HTTP.

Refira-se a Nest JS - Controllers para mais informações.

Predefinidos

Classe Arquivo Descrição
OrmController ormcontroller.ts Extenda essa classe para já criar métodos GET, GET:id, POST, PUT, PUT:id, e DELETE:id.
Aplicável apenas se o ORM estiver habilitado.

Exemplos

Customizado:

@Controller('user')
export class UserController {

  public constructor(private readonly userService: UserService) { }

  @Get(':id')
  public async getUser(@Param('id') id: string): Promise<UserData> {
    return this.userService.readUser(id);
  }
  
}

Basedo em um serviço de ORM:

@Controller('user')
export class UserController extends OrmController<UserEntity> {

  public constructor(private readonly userService: UserService) {
    super(userService);

    this.options = {
      dto: {
        read: UserReadDto,
        create: UserCreateDto,
        update: UserUpdateDto,
      },
    };

  }

}

DTO

Data Transfer Objects definem a estrutura de como os dados são transferidos.

Ao criar controllers que recebem dados, você pode injetá-los em argumentos via decorators @Body(), @Query() e @Param().

Uma vez que definir o tipo deste argumentos como uma classe DTO decorada com opções da biblioteca class-validator, suas requisições serão automaticamente validadas.

Crie-os em pastas *.dto em arquivos sobre a que método se referem, por exemplo *.create.dto.ts ou *.update.dto.ts.

Refira-se a Nest JS - Auto Validation para mais informações.

Lista completa de decorators disponíveis em Class Validator - Decorator Reference.

Exemplos

DTO para criação de usuário:

export class UserCreateDto {

  @IsString() @IsNotEmpty()
  public name: string;

  @IsNumber() @Min(1)
  public age: string;

}

Definição de tipo no controller:

@Controller('user')
export class UserController {

  public constructor(private readonly userService: UserService) { }

  @Post()
  // O fato de definirmos UserCreateDto a seguir irá validar a requisição automaticamente
  public async postUser(@Body() body: UserCreateDto): Promise<UserData> {
    return this.userService.createUser(body);
  }
  
}

Providers

Serviços são componentes que executam um ou mais dentre extração, transformação e carregamento de dados.

A instanciação de suas classes são gerenciadas pela injeção de depências do framwork, por isso devem ser decorados com @Injectable().

Eles podem ter vários tipos includindo Middlewares, Interceptors, Filters, etc, explicados mais adiante.

Para o serviço principal do seu domínio, utilize o padrão *.service.ts de nomenclatura e importe-o na propriedade providers do módulo respectivo.

Caso deseje que outros módulos tenha acesso ao seu serviço, é necessário exportá-lo na propriedade exports

Refira-se a Nest JS - Providers para mais informações.

Predefinidos

Classe Arquivo Descrição
AppProvider abstract.provider.ts Extenda essa classe para já ter acesso ao logger, variáveis de ambiente e método de retry.
OrmService ormservice.ts Extenda essa classe para já ter acesso a métodos de manipulação de dados via ORM com gerenciamento de exeções nas queries.
HttpsService https.service.ts Wrapper sobre o Axios para padronizar exceções HTTP e adicionar amenidades.
LoggerService logger.service.ts Disponível via AppProvider, realiza integração com Sentry e imprime no console durante desenvolvimento.
RedisService redis.service.ts Wrapper sobre o redis para ler e persistir dados chave/valor em cloud.

Exemplos

Customizado:

@Injectable()
export class UserService {

  /** Implemente seus métodos */

  public async userHello(): Promise<void> {
    this.logger.debug('hello world');
  }

}

Baseado em serviço ORM:

@Injectable()
export class UserService extends OrmService<UserEntity> {

  public constructor(
    @InjectRepository(UserEntity)
    private readonly userRepository: EntityRepository<UserEntity>,
  ) {
    super(userRepository, {
      uniqueKey: [ 'name' ],
      populate: [ 'team' ],
    });
  }

}

Middlewares

Executam procedimentos logo que uma requisição HTTP entra e antes de chegar nos interceptors ou controllers.

Devem ser decoradas com @Injectable(), implementar NestMiddleware e serem aplicadas via consumer a nível de módulo.

Refira-se a Nest JS - Middleware para mais informações.

Predefinidos

Classe Arquivo Descrição
AppAuthMiddleware app.metadata.middleware.ts Extrai o IP e User Agent da requisição e os separa em uma propriedade de metadados

Exemplos

Implementação:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from '../app.interface';

@Injectable()
export class AppMetadataMiddleware implements NestMiddleware {

  public async use(req: Request, res: Response, next: any): Promise<void> {
    req['metadata'] = {
      ip: requestIp.getClientIp(req) || null,
      userAgent: req.headers ? req.headers['user-agent'] : null,
    };
    next();
  }

}

Importação:

export class AppModule {

  public configure(consumer: MiddlewareConsumer): void {
    consumer
      .apply(
        // Aplique quantos quiser em ordem
        AppMetadataMiddleware,
      )
      // Você pode escolher as rotas com:
      // .forRoutes({ method, path }, { method, path });
      .forRoutes('*');
  }

}

Interceptors

Permitem que manipule dados de uma mesma requisição antes de ela atingir um controller bem como após ser tratada por ele.

Úteis caso precise rastrear a trajetória de uma requisição ou aplicar de maneira unifica algo durante seu retorno.

Seguem o padrão *.interceptor.ts, e devem ser decorados por Injectable() bem como importados no respectivo módulo.

Também devem implementar a classe NestInterceptor e retornar um Observable o que permite rastrear a requisição e utilizar métodos rxjs em seu témino.

Refira-se a Nest JS - Interceptors para mais informações.

Predefinidos

Classe Arquivo Descrição
AppLoggerInterceptor app.logger.interceptor.ts Extrai o IP da requisição bem faz o log da entrada e saída da requisição.
AbstractEntityInterceptor ormentity.interceptor.ts Executa o método toJSON() das entidades antes de retorná-las. Aplicável apenas se utilizado o ORM.

Exemplo

Implementação:

@Injectable()
export class AppLoggerInterceptor implements NestInterceptor {

  public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const req: AppRequest = context.switchToHttp().getRequest();

    this.logger.server(`Incoming ${req.url}`);

    return next
      .handle()
      .pipe(
        // Utilize dentro do .pipe qualquer método rxjs
        finalize(() => {
          const res: AppResponse = context.switchToHttp().getResponse();
          this.logger.server(`Finished ${req.url} with status ${res.statusCode}`);
        }),
      );
  }

}

Importação:

@Module({
  providers: [
    { provide: APP_INTERCEPTOR, useClass: AppLoggerInterceptor },
  ],
})
export class AppModule { }

Filters

Filtros são decorados com @Catch() e servem o propósito de capturar execeções durante execução do contexto.

Neste boilerplate utilizamos um a nível global para padronização de retorno HTTP, mas eles podem ser individualizados a nível de módulo.

Refira-se a Nest JS - Exception Filters para mais informações.

Predefinidos

Classe Arquivo Descrição
AppFilter app.filter.ts Padroniza o retorno HTTP em caso de exceções.

Testes

Os testes são baseados no framework Jest, todavia para total isolamento nas execuções, o NestJS provê um pacote para compilação individual de módulos temporários @nestjs/testing.

Para usufruir desta funcionalidade, configure seus testes com um método beforeEach() implementando createTestingModule() conforme exemplo a seguir:

import { Test } from '@nestjs/testing';
import { RedisService } from './redis.service';

describe('RedisService', () => {
  const rng = Math.random();
  let redisService: RedisService;

  beforeAll(async() => {
    const testModule = await Test.createTestingModule({
      providers: [ RedisService ],
    }).compile();

    redisService = testModule.get(RedisService);
  });

  describe('setKey', () => {
    it('should persist a random number', async() => {
      expect(await redisService.setKey('TEST_KEY', { rng }, 10 * 1000))
        .toBeUndefined();
    });
  });

  describe('getKey', () => {
    it('should read persisted random number', async() => {
      expect(await redisService.getKey('TEST_KEY'))
        .toMatchObject({ rng });
    });
  });
});

Agora basta executar npm run test e todos os testes descritos em arquivos *.spec.ts serão aplicados.

Migrations

Para utilizar migrations com versionamento e rollback refira-se a documentação oficial: Mikro ORM - Migrations

Este boilerplate permite sincronizar a modelagem atual com as entidades definidas nos arquivos *.entity.ts.

Por padrão, é possível possuir até três ambientes configurados em arquivos .env separados sendo:

.env              # Desenvolvimento
.env.staging      # Homologação
.env.production   # Produção

Para realizar a migração de sincronismo execute:

npm run orm:sync:dev  # Utiliza arquivo .env
npm run orm:sync:stg  # Utiliza arquivo .env.staging
npm run orm:sync:prd  # Utiliza arquivo .env.production

Caso prefira apenas visualizar a migração e não executá-la, substitua o script orm:sync por orm:dump. As queries serão impressas no console.

Templates

Criar serviços, controllers e DTOs de algo repetitivo pode ser bastante tedioso, sendo assim foi desenvolvido uma maneira mais simples para realizar estas implementações.

API

Cria todos os arquivos recomendados para implementação de um serviço de API externo.

Dado um domínio de sua escolha, por exemplo: gmaps, execute o script:

npm run template:api -- -n gmaps

A seguinte estrutura de arquivos será criada dentro de /source:

gmaps
|- gmaps.dto
   | - index.ts
|- gmaps.interface
   | - index.ts
|- gmaps.service.ts
|- gmaps.config.ts

Agora, por padrão, as variáveis de ambiente GMAPS_HOST e GMAPS_AUTH serão mandatórias. Você pode configurar a validação no arquivo gmaps.config.ts.

A estratégia de autenticação do modelo é colocar o *_AUTH no header Authorization. Dependendo do serviço será necessário modificar.

CRUD

Cria todos os arquivos recomendados para implementação de uma entidade com métodos HTTP de GET, GET:id, POST, PUT, PUT:id, e DELETE:id.

Dado um domínio de sua escolha, por exemplo: user, execute o script:

npm run template:crud -- -n user

A seguinte estrutura de arquivos será criada dentro de /source:

user
|- user.dto
   | - index.ts
   | - user.create.dto.ts
   | - user.read.dto.ts
   | - user.update.dto.ts
|- user.controller.ts
|- user.entity.ts
|- user.module.ts
|- user.service.ts

Agora é só definir a entidade em user.entity.ts e configurar a validação dos dtos que todos os métodos estarão implementados.

About

Full back-end boilerplate based on NestJS and MikroORM. Written in TypeScript.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published