Pular para o conteúdo

NestJS

Framework Node.js progressivo com TypeScript em primeiro lugar para construir aplicações escaláveis do lado do servidor.

ComandoDescrição
npm i -g @nestjs/cliInstalar CLI do NestJS globalmente
nest new project-nameCriar um novo projeto NestJS
nest new project-name --strictCriar projeto com TypeScript estrito
nest new project-name -p pnpmCriar projeto usando pnpm
nest new project-name -p yarnCriar projeto usando Yarn
ComandoDescrição
npm run start:devIniciar servidor de desenvolvimento com hot reload
npm run start:debugIniciar com modo debug e hot reload
npm run start:prodIniciar servidor de produção
npm run buildCompilar o projeto
npm run testExecutar testes unitários
npm run test:watchExecutar testes em modo watch
npm run test:covExecutar testes com relatório de cobertura
npm run test:e2eExecutar testes end-to-end
npm run lintAnalisar código fonte
src/
├── app.controller.ts       # Controlador raiz
├── app.controller.spec.ts  # Teste unitário do controlador
├── app.module.ts           # Módulo raiz
├── app.service.ts          # Serviço raiz
├── main.ts                 # Ponto de entrada da aplicação
├── users/
│   ├── users.module.ts     # Módulo de funcionalidade
│   ├── users.controller.ts # Controlador de funcionalidade
│   ├── users.service.ts    # Serviço de funcionalidade
│   ├── dto/
│   │   ├── create-user.dto.ts
│   │   └── update-user.dto.ts
│   ├── entities/
│   │   └── user.entity.ts
│   └── users.controller.spec.ts
├── auth/
│   ├── auth.module.ts
│   ├── auth.guard.ts
│   ├── auth.service.ts
│   └── strategies/
│       └── jwt.strategy.ts
└── common/
    ├── filters/
    │   └── http-exception.filter.ts
    ├── interceptors/
    │   └── logging.interceptor.ts
    └── pipes/
        └── validation.pipe.ts
ComandoDescrição
nest generate module usersGerar um novo módulo
nest generate controller usersGerar um novo controlador
nest generate service usersGerar um novo serviço
nest generate resource usersGerar recurso CRUD completo (módulo + controlador + serviço + DTOs)
nest generate guard authGerar um guard de autenticação
nest generate pipe validationGerar um pipe de validação
nest generate interceptor loggingGerar um interceptor
nest generate middleware loggerGerar middleware
nest generate filter http-exceptionGerar filtro de exceção
nest generate decorator rolesGerar um decorator personalizado
nest generate class user.entityGerar uma classe simples
nest generate interface userGerar uma interface
nest g res users --no-specGerar recurso sem arquivos de teste
nest g mo users --flatGerar sem criar subpasta
nest infoExibir informações do projeto e dependências
# Recurso CRUD completo (fluxo de trabalho mais comum)
nest g resource users

# Prompts do CLI:
# ? What transport layer do you use? REST API
# ? Would you like to generate CRUD entry points? Yes

# Cria:
# src/users/users.module.ts
# src/users/users.controller.ts
# src/users/users.service.ts
# src/users/dto/create-user.dto.ts
# src/users/dto/update-user.dto.ts
# src/users/entities/user.entity.ts
# src/users/users.controller.spec.ts
# Atualiza: src/app.module.ts (importa UsersModule)
ComandoDescrição
@Module({ imports: [UsersModule] })Importar outro módulo
@Module({ controllers: [UsersController] })Registrar controladores
@Module({ providers: [UsersService] })Registrar providers/serviços
@Module({ exports: [UsersService] })Exportar providers para outros módulos
@Global()Tornar módulo disponível globalmente (usar com moderação)
import { Module, DynamicModule } from '@nestjs/common'

@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseOptions): DynamicModule {
    return {
      module: DatabaseModule,
      global: true,
      providers: [
        { provide: 'DATABASE_OPTIONS', useValue: options },
        DatabaseService,
      ],
      exports: [DatabaseService],
    }
  }

  static forRootAsync(options: DatabaseAsyncOptions): DynamicModule {
    return {
      module: DatabaseModule,
      global: true,
      imports: options.imports || [],
      providers: [
        {
          provide: 'DATABASE_OPTIONS',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    }
  }
}

// Uso no AppModule
@Module({
  imports: [
    DatabaseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
ComandoDescrição
@Controller('users')Definir controlador com prefixo de rota
@Get()Tratar requisição GET
@Post()Tratar requisição POST
@Put(':id')Tratar requisição PUT com parâmetro
@Patch(':id')Tratar requisição PATCH
@Delete(':id')Tratar requisição DELETE
@All('*')Tratar todos os métodos HTTP
ComandoDescrição
@Param('id') id: stringExtrair parâmetro de rota
@Param('id', ParseIntPipe) id: numberExtrair e converter para inteiro
@Query('page') page: stringExtrair parâmetro de consulta
@Query() query: PaginationDtoExtrair todos os parâmetros de consulta como DTO
@Body() dto: CreateUserDtoExtrair e tipar corpo da requisição
@Body('email') email: stringExtrair campo específico do corpo
@Headers('authorization') auth: stringExtrair cabeçalho da requisição
@Ip() ip: stringExtrair endereço IP do cliente
@Req() request: RequestAcessar objeto de requisição bruto
@Res() response: ResponseAcessar objeto de resposta bruto
@HttpCode(201)Definir código de status HTTP personalizado
@Header('Cache-Control', 'none')Definir cabeçalho de resposta
@Redirect('/new-url', 301)Resposta de redirecionamento
import {
  Controller, Get, Post, Put, Delete, Param, Query, Body,
  HttpCode, HttpStatus, ParseIntPipe, UseGuards, UseInterceptors,
} from '@nestjs/common'
import { UsersService } from './users.service'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
import { LoggingInterceptor } from '../common/interceptors/logging.interceptor'

@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto)
  }

  @Get()
  findAll(
    @Query('page', new ParseIntPipe({ optional: true })) page = 1,
    @Query('limit', new ParseIntPipe({ optional: true })) limit = 10,
  ) {
    return this.usersService.findAll(page, limit)
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id)
  }

  @Put(':id')
  @UseGuards(JwtAuthGuard)
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateUserDto: UpdateUserDto,
  ) {
    return this.usersService.update(id, updateUserDto)
  }

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}
ComandoDescrição
@Injectable()Marcar classe como provider injetável
constructor(private usersService: UsersService)Injetar serviço via construtor
@Inject('TOKEN') private configInjetar por token personalizado
{ provide: 'TOKEN', useValue: value }Registrar provider de valor
{ provide: 'TOKEN', useFactory: () => ... }Registrar provider de fábrica
{ provide: 'TOKEN', useClass: MyClass }Registrar provider de classe
{ provide: 'TOKEN', useExisting: OtherService }Alias para provider existente
@Optional() private service?: MyServiceInjeção de dependência opcional
{ provide: 'TOKEN', scope: Scope.REQUEST }Provider com escopo de requisição
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepo: Repository<User>,
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const exists = await this.usersRepo.findOneBy({ email: dto.email })
    if (exists) {
      throw new ConflictException('Email already registered')
    }
    const user = this.usersRepo.create(dto)
    return this.usersRepo.save(user)
  }

  async findAll(page: number, limit: number) {
    const [items, total] = await this.usersRepo.findAndCount({
      skip: (page - 1) * limit,
      take: limit,
      order: { createdAt: 'DESC' },
    })
    return { items, total, page, limit }
  }

  async findOne(id: number): Promise<User> {
    const user = await this.usersRepo.findOneBy({ id })
    if (!user) throw new NotFoundException(`User #${id} not found`)
    return user
  }

  async update(id: number, dto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id)
    Object.assign(user, dto)
    return this.usersRepo.save(user)
  }

  async remove(id: number): Promise<void> {
    const result = await this.usersRepo.delete(id)
    if (result.affected === 0) {
      throw new NotFoundException(`User #${id} not found`)
    }
  }
}
npm install class-validator class-transformer
// main.ts - habilitar validação global
import { ValidationPipe } from '@nestjs/common'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,           // remover propriedades desconhecidas
    forbidNonWhitelisted: true, // lançar erro em propriedades desconhecidas
    transform: true,           // transformar automaticamente payloads para tipos DTO
    transformOptions: {
      enableImplicitConversion: true, // converter query strings para números/booleanos
    },
  }))
  await app.listen(3000)
}
import {
  IsString, IsEmail, IsOptional, IsInt, Min, Max,
  MinLength, MaxLength, IsEnum, ValidateNested, IsArray,
} from 'class-validator'
import { Type } from 'class-transformer'
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types'

export class CreateUserDto {
  @IsString()
  @MinLength(2)
  @MaxLength(50)
  name: string

  @IsEmail()
  email: string

  @IsString()
  @MinLength(8)
  password: string

  @IsOptional()
  @IsEnum(Role)
  role?: Role

  @IsOptional()
  @ValidateNested()
  @Type(() => AddressDto)
  address?: AddressDto
}

// Gerar automaticamente DTO de atualização (todos os campos opcionais)
export class UpdateUserDto extends PartialType(CreateUserDto) {}

// Selecionar campos específicos
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}

// Omitir campos específicos
export class PublicUserDto extends OmitType(CreateUserDto, ['password']) {}
ComandoDescrição
@UseGuards(AuthGuard)Aplicar guard ao controlador ou rota
@UseGuards(AuthGuard, RolesGuard)Encadear múltiplos guards
app.useGlobalGuards(new AuthGuard())Registrar guard global
canActivate(context: ExecutionContext)Implementar lógica do guard
@SetMetadata('roles', ['admin'])Definir metadados personalizados para guards
ReflectorLer metadados nos guards
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
// auth/auth.module.ts
@Module({
  imports: [
    UsersModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        secret: config.get('JWT_SECRET'),
        signOptions: { expiresIn: '1h' },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

// auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(config: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get('JWT_SECRET'),
    })
  }

  validate(payload: { sub: number; email: string }) {
    return { id: payload.sub, email: payload.email }
  }
}

// auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

// Uso
@Controller('profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@Req() req) {
    return req.user // injetado por JwtStrategy.validate()
  }
}
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common'
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)

// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'
import { Reflector } from '@nestjs/core'

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ])
    if (!requiredRoles) return true
    const { user } = context.switchToHttp().getRequest()
    return requiredRoles.some(role => user.roles?.includes(role))
  }
}

// Uso
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
  @Get('dashboard')
  @Roles('admin')
  getDashboard() {
    return { message: 'Admin dashboard' }
  }

  @Delete('users/:id')
  @Roles('admin', 'superadmin')
  removeUser(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}
import { Injectable, NestMiddleware } from '@nestjs/common'
import { Request, Response, NextFunction } from 'express'

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now()
    res.on('finish', () => {
      const duration = Date.now() - start
      console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`)
    })
    next()
  }
}

// Registrar no módulo
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*')
  }
}
import {
  Injectable, NestInterceptor, ExecutionContext, CallHandler,
} from '@nestjs/common'
import { Observable, map, tap } from 'rxjs'

// Transformar formato da resposta
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<{ data: T }> {
    return next.handle().pipe(
      map(data => ({ data, timestamp: new Date().toISOString() })),
    )
  }
}

// Medir tempo de execução
@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now()
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start
        const response = context.switchToHttp().getResponse()
        response.setHeader('X-Response-Time', `${duration}ms`)
      }),
    )
  }
}
ComandoDescrição
npm install @nestjs/typeorm typeorm pgInstalar TypeORM com PostgreSQL
TypeOrmModule.forRoot({ type: 'postgres', ... })Configurar conexão no AppModule
TypeOrmModule.forFeature([User])Registrar entidade no módulo de funcionalidade
@InjectRepository(User) private repo: Repository<User>Injetar repositório
npm install @prisma/client
npm install -D prisma
npx prisma init
// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect()
  }

  async onModuleDestroy() {
    await this.$disconnect()
  }
}

// prisma/prisma.module.ts
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

// Uso em um serviço
@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  findAll() {
    return this.prisma.user.findMany()
  }

  findOne(id: number) {
    return this.prisma.user.findUniqueOrThrow({ where: { id } })
  }
}
ComandoDescrição
throw new BadRequestException('Invalid input')400 Requisição Inválida
throw new UnauthorizedException('Login required')401 Não Autorizado
throw new ForbiddenException('Access denied')403 Proibido
throw new NotFoundException('User not found')404 Não Encontrado
throw new ConflictException('Email taken')409 Conflito
throw new UnprocessableEntityException('Validation failed')422 Entidade Não Processável
throw new InternalServerErrorException()500 Erro Interno do Servidor
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
} from '@nestjs/common'
import { Request, Response } from 'express'

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Internal server error'

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: typeof message === 'string' ? message : (message as any).message,
    })
  }
}

// Registrar globalmente no main.ts
app.useGlobalFilters(new AllExceptionsFilter())
npm install @nestjs/config
// app.module.ts
import { ConfigModule, ConfigService } from '@nestjs/config'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_URL: Joi.string().required(),
        JWT_SECRET: Joi.string().required(),
      }),
    }),
  ],
})
export class AppModule {}

// Uso em qualquer serviço
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}

  getPort(): number {
    return this.config.get<number>('PORT', 3000)
  }
}
import { Test, TestingModule } from '@nestjs/testing'
import { UsersService } from './users.service'
import { getRepositoryToken } from '@nestjs/typeorm'
import { User } from './entities/user.entity'

describe('UsersService', () => {
  let service: UsersService

  const mockRepository = {
    create: jest.fn(),
    save: jest.fn(),
    findOneBy: jest.fn(),
    findAndCount: jest.fn(),
    delete: jest.fn(),
  }

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        { provide: getRepositoryToken(User), useValue: mockRepository },
      ],
    }).compile()

    service = module.get<UsersService>(UsersService)
  })

  it('should create a user', async () => {
    const dto = { name: 'Alice', email: 'alice@example.com', password: 'secret123' }
    mockRepository.findOneBy.mockResolvedValue(null)
    mockRepository.create.mockReturnValue(dto)
    mockRepository.save.mockResolvedValue({ id: 1, ...dto })

    const result = await service.create(dto)
    expect(result.id).toBe(1)
    expect(mockRepository.save).toHaveBeenCalled()
  })
})
import { Test, TestingModule } from '@nestjs/testing'
import { INestApplication, ValidationPipe } from '@nestjs/common'
import * as request from 'supertest'
import { AppModule } from '../src/app.module'

describe('UsersController (e2e)', () => {
  let app: INestApplication

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleFixture.createNestApplication()
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }))
    await app.init()
  })

  afterAll(async () => {
    await app.close()
  })

  it('/users (POST) creates a user', () => {
    return request(app.getHttpServer())
      .post('/users')
      .send({ name: 'Alice', email: 'alice@example.com', password: 'secret123' })
      .expect(201)
      .expect((res) => {
        expect(res.body.name).toBe('Alice')
        expect(res.body.id).toBeDefined()
      })
  })

  it('/users (GET) returns paginated list', () => {
    return request(app.getHttpServer())
      .get('/users?page=1&limit=10')
      .expect(200)
      .expect((res) => {
        expect(res.body.items).toBeInstanceOf(Array)
        expect(res.body.total).toBeDefined()
      })
  })
})
  1. Use o gerador de recursosnest g resource cria todo o scaffold CRUD (módulo, controlador, serviço, DTOs, entidade, teste) em segundos e mantém seu projeto consistente.

  2. Habilite ValidationPipe global com whitelist — configure whitelist: true e forbidNonWhitelisted: true para automaticamente remover ou rejeitar campos inesperados de todas as requisições.

  3. Use tipos mapeados para DTOsPartialType, PickType, OmitType e IntersectionType mantêm seus DTOs DRY derivando DTOs de atualização/consulta do DTO de criação.

  4. Prefira injeção por construtor — deixe o NestJS lidar com a injeção de dependência através de construtores em vez de usar tokens manuais @Inject(), a menos que precise de providers personalizados.

  5. Mantenha controladores leves — controladores devem apenas analisar entrada e retornar saída. Mova toda a lógica de negócio para serviços para testabilidade e reutilização.

  6. Use ConfigModule para todo acesso a ambiente — nunca leia process.env diretamente em serviços. Use ConfigService com schemas de validação para que variáveis ausentes falhem rapidamente na inicialização.

  7. Defina o escopo de guards e interceptors adequadamente — aplique-os no nível do controlador ou rota com decorators em vez de globalmente, a menos que realmente se apliquem em todo lugar.

  8. Escreva testes unitários e E2E — teste unitariamente serviços com dependências mockadas usando Test.createTestingModule(), e teste E2E endpoints HTTP com supertest.

  9. Use Prisma ou TypeORM consistentemente — escolha um ORM e use-o em todo o projeto. Prisma oferece melhor segurança de tipos; TypeORM oferece mais flexibilidade com decorators.

  10. Estruture por funcionalidade, não por tipo — agrupe arquivos por domínio (users/, auth/, orders/) em vez de por tipo (controllers/, services/). Isso mantém o código relacionado junto e os módulos autocontidos.

  11. Trate erros com exceções — lance exceções HTTP integradas (NotFoundException, ConflictException) dos serviços. Use filtros de exceção para formatação personalizada de erros.

  12. Use async/await consistentemente — NestJS lida com promises nativamente. Retorne valores assíncronos dos métodos de serviço e deixe o framework serializar a resposta.