<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Criar uma REST API completa com Nestjs: O guia definitivo sobre Typeorm, JWT, RBAC e segurança

Introdução

O NestJS é hoje a framework de backend Node.js mais usada para construir aplicações server-side escaláveis, tipadas e fáceis de manter. Combina os conceitos mais sólidos da engenharia de software — Dependency Injection, módulos, arquitetura em camadas — com a produtividade do TypeScript e um ecossistema que cobre praticamente todas as necessidades: REST, GraphQL, WebSocket, microsserviços, CLI, cron jobs.

Neste guia construímos, passo a passo, uma REST API completa e pronta para produção: uma API de Blog com autenticação JWT, refresh token, controlo de acessos baseado em roles (RBAC), validação, upload de ficheiros, logging centralizado e gestão estruturada de erros — exatamente a stack por trás da maioria das APIs NestJS reais em 2026.

O que é o NestJS

O NestJS é uma framework opinionated construída sobre o Express (ou, alternativamente, o Fastify) que impõe uma estrutura precisa à aplicação: cada funcionalidade é organizada em módulos, cada módulo expõe controllers (tratam os pedidos HTTP) e providers (contêm a lógica de negócio, tipicamente services), ligados entre si através de um sistema de Dependency Injection integrado e inspirado no Angular.

Porque Usá-lo

  • TypeScript nativo: tipagem end-to-end, autocompletar, refactoring seguro.
  • Arquitetura imposta: ao contrário do Express puro, o NestJS obriga à separação de responsabilidades (controller/service/repository), reduzindo o "big ball of mud" típico de projetos Node que cresceram sem estrutura.
  • Dependency Injection integrada: testabilidade elevada, baixo acoplamento, providers intercambiáveis (útil para mocks nos testes).
  • Ecossistema maduro: módulos oficiais para TypeORM, Prisma, Mongoose, Passport, GraphQL, WebSocket, Bull/BullMQ, Swagger, gRPC, microsserviços.
  • Decoradores declarativos: guards, interceptors, pipes e exception filters permitem cross-cutting concerns (auth, logging, validação) sem poluir a lógica de negócio.

Quando Compensa (e Quando Não)

O NestJS compensa quando o projeto tem uma complexidade que justifica uma estrutura rígida: equipas com vários programadores, APIs destinadas a crescer ao longo do tempo, necessidade de testabilidade elevada, aplicações enterprise com requisitos de segurança e compliance. É provavelmente excessivo para um script pontual ou um protótipo que será descartado numa semana — aí, o Express puro ou o Fastify "nu" continuam a ser mais rápidos de arrancar.

Vantagens e Desvantagens

VantagensDesvantagens
Estrutura clara e consistente entre projetos diferentesCurva de aprendizagem mais acentuada do que o Express puro (decoradores, DI, módulos)
Testabilidade nativa graças ao DIOverhead de boilerplate em projetos muito pequenos
Ecossistema oficial amplo e bem mantidoMais "magia" (decoradores, reflection) em comparação com código explícito
Ótima integração com TypeScript e Swagger/OpenAPIBundle size e cold start ligeiramente superiores a frameworks minimalistas (relevante em ambientes serverless)
Facilita a adoção de Clean Architecture / hexagonalRequer disciplina da equipa para não abusar da flexibilidade dos módulos

Casos Reais

O NestJS é usado em produção por empresas como Adidas, Roche, Autodesk e Decathlon, além de ser uma escolha muito comum para backends SaaS B2B, plataformas de e-commerce, sistemas de gestão de utilizadores e APIs de microsserviços que precisam de comunicar entre si via gRPC ou filas de mensagens (RabbitMQ, Kafka).

Pré-requisitos

Antes de começar, certifica-te de que tens estas ferramentas instaladas e conheces o básico de TypeScript (interfaces, decoradores, generics) e conceitos REST (verbos HTTP, status codes, idempotência).

FerramentaVersão recomendadaPara que serve
Node.js20.x LTS ou superiorRuntime JavaScript sobre o qual o NestJS corre
npm10.x (incluído no Node 20)Gestão de pacotes
NestJS CLI@nestjs/cli 10.x+Scaffolding de módulos, controllers, services
TypeScript5.xLinguagem em que o NestJS e a tua app estão escritos
VS CodeÚltima versão estávelEditor com suporte nativo para TypeScript
PostgreSQL15.x ou superiorBase de dados relacional para o exemplo com TypeORM
Docker (opcional)24.x+Executar o PostgreSQL localmente sem instalação nativa
# Verifica as versões instaladas
node -v
npm -v

# Instala a CLI do NestJS globalmente
npm install -g @nestjs/cli

nest --version

Sugestão: se não quiseres instalar o PostgreSQL nativamente, arranca-o com Docker: docker run --name pg-blog -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=blog_db -p 5432:5432 -d postgres:16.

Arquitetura

Antes de escrever código, é fundamental perceber como o NestJS encaminha um pedido HTTP através dos seus componentes principais. Eis o ciclo de vida completo de um pedido:

Client
  │
  ▼
┌─────────────┐
│  Middleware  │  (ex. logger, cookie-parser — executado antes do routing)
└──────┬───────┘
       ▼
┌─────────────┐
│    Guard     │  (ex. AuthGuard — decide se o pedido pode prosseguir)
└──────┬───────┘
       ▼
┌─────────────┐
│ Interceptor  │  (fase "antes" — ex. logging, transformação do input)
└──────┬───────┘
       ▼
┌─────────────┐
│     Pipe     │  (valida e transforma os parâmetros de entrada)
└──────┬───────┘
       ▼
┌─────────────┐
│  Controller  │  (recebe o pedido, delega ao service)
└──────┬───────┘
       ▼
┌─────────────┐
│   Service    │  (lógica de negócio, chama os repositories)
└──────┬───────┘
       ▼
┌─────────────┐
│ Repository / │  (acesso aos dados — TypeORM, Prisma, Mongoose...)
│   Database   │
└──────┬───────┘
       ▼
┌─────────────┐
│ Interceptor  │  (fase "depois" — ex. transformação da resposta)
└──────┬───────┘
       ▼
┌──────────────────┐
│ Exception Filter  │  (intercepta APENAS se for lançada uma exceção)
└──────┬────────────┘
       ▼
    Response

Controller

A camada mais externa: recebe os pedidos HTTP, extrai parâmetros/body/query e delega imediatamente a lógica ao service correspondente. Um controller nunca deve conter lógica de negócio — a sua única responsabilidade é o mapeamento HTTP ↔ chamada de método.

Service

Contém a lógica de negócio propriamente dita. É um provider injetável, tipicamente marcado com @Injectable(), e é injetado no controller (ou noutros services) através do construtor.

Module

O módulo é a unidade organizacional do NestJS: agrupa controllers, providers e imports de outros módulos. Toda a aplicação tem pelo menos um AppModule raiz, e as funcionalidades são tipicamente isoladas em feature modules (ex. PostsModule, AuthModule, UsersModule).

Provider

Qualquer classe gerida pelo contentor de Dependency Injection do Nest: services, repositories, factories, helpers. É declarado em providers no módulo e pode ser injetado onde estiver disponível (no mesmo módulo, ou exportado para outros módulos).

Middleware

Funções executadas antes do routing do Nest, com acesso direto a req, res e next() — o mesmo modelo do Express. Útil para logging bruto, parsing de cookies, ou headers customizados aplicados globalmente.

Guard

Decidem se um pedido pode prosseguir, devolvendo true/ false (ou lançando uma exceção). São o local correto para autenticação e autorização — nunca dentro do controller ou do service.

Interceptor

Posicionam-se em torno da execução do handler da rota (como middleware AOP): podem transformar o pedido antes de chegar ao controller e a resposta antes de sair. Casos de uso típicos: logging dos tempos de resposta, transformação uniforme das respostas, caching, gestão de timeouts.

Pipe

Transformam e validam os dados de entrada (parâmetros de rota, query string, body) antes de chegarem ao controller. O ValidationPipe, integrado com class-validator, é o pipe mais usado em qualquer API NestJS séria.

Exception Filter

Interceptam exceções lançadas em qualquer ponto do pedido e transformam-nas numa resposta HTTP consistente (status code, corpo JSON estruturado), evitando que stack traces ou erros em bruto cheguem ao cliente.

Instalação

# 1. Cria um novo projeto NestJS
nest new blog-api

# Durante a criação, escolhe npm como package manager quando solicitado

cd blog-api

# 2. Instala as dependências para a base de dados (TypeORM + driver PostgreSQL)
npm install @nestjs/typeorm typeorm pg

# 3. Instala as dependências para a validação
npm install class-validator class-transformer

# 4. Instala as dependências para autenticação JWT
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install --save-dev @types/passport-jwt @types/bcrypt

# 5. Instala as dependências para segurança e rate limiting
npm install helmet @nestjs/throttler

# 6. Instala as dependências para upload de ficheiros
npm install @nestjs/platform-express multer
npm install --save-dev @types/multer

# 7. Instala o Swagger para documentação automática da API
npm install @nestjs/swagger

# 8. Instala o logger estruturado
npm install nestjs-pino pino-http pino-pretty

# 9. Configura as variáveis de ambiente
npm install @nestjs/config

Cada comando instala um bloco funcional bem preciso: @nestjs/typeorm + typeorm + pg ligam o Nest ao PostgreSQL através do ORM TypeORM; class-validator/class-transformer ativam DTOs validados automaticamente; a stack passport/passport-jwt/bcrypt constrói todo o fluxo de autenticação; helmet e @nestjs/throttler reforçam a segurança HTTP e o rate limiting; multer trata o multipart/form-data para uploads; nestjs-pino fornece logging estruturado em JSON, adequado para produção.

Implementação Passo a Passo

1. Estrutura de Pastas

src/
├── main.ts
├── app.module.ts
├── config/
│   └── configuration.ts
├── common/
│   ├── filters/
│   │   └── http-exception.filter.ts
│   ├── interceptors/
│   │   ├── logging.interceptor.ts
│   │   └── transform.interceptor.ts
│   ├── guards/
│   │   └── roles.guard.ts
│   ├── decorators/
│   │   └── roles.decorator.ts
│   └── pipes/
│       └── parse-object-id.pipe.ts
├── auth/
│   ├── auth.module.ts
│   ├── auth.controller.ts
│   ├── auth.service.ts
│   ├── strategies/
│   │   ├── jwt.strategy.ts
│   │   └── jwt-refresh.strategy.ts
│   └── dto/
│       ├── login.dto.ts
│       └── register.dto.ts
├── users/
│   ├── users.module.ts
│   ├── users.service.ts
│   └── entities/
│       └── user.entity.ts
└── posts/
    ├── posts.module.ts
    ├── posts.controller.ts
    ├── posts.service.ts
    ├── entities/
    │   └── post.entity.ts
    └── dto/
        ├── create-post.dto.ts
        └── update-post.dto.ts

2. Configuração Centralizada com @nestjs/config

// src/config/configuration.ts
export default () => ({
  port: parseInt(process.env.PORT ?? '3000', 10),
  database: {
    host: process.env.DB_HOST ?? 'localhost',
    port: parseInt(process.env.DB_PORT ?? '5432', 10),
    username: process.env.DB_USERNAME ?? 'postgres',
    password: process.env.DB_PASSWORD ?? 'postgres',
    name: process.env.DB_NAME ?? 'blog_db',
  },
  jwt: {
    accessSecret: process.env.JWT_ACCESS_SECRET ?? 'change-me-access',
    accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN ?? '15m',
    refreshSecret: process.env.JWT_REFRESH_SECRET ?? 'change-me-refresh',
    refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN ?? '7d',
  },
});
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ThrottlerModule } from '@nestjs/throttler';
import configuration from './config/configuration';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { PostsModule } from './posts/posts.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => ({
        type: 'postgres',
        host: cfg.get('database.host'),
        port: cfg.get('database.port'),
        username: cfg.get('database.username'),
        password: cfg.get('database.password'),
        database: cfg.get('database.name'),
        autoLoadEntities: true,
        synchronize: process.env.NODE_ENV !== 'production', // ⚠️ apenas em desenvolvimento
      }),
    }),
    ThrottlerModule.forRoot([{ ttl: 60000, limit: 100 }]),
    AuthModule,
    UsersModule,
    PostsModule,
  ],
})
export class AppModule {}

Linha a linha: ConfigModule.forRoot({ isGlobal: true }) torna o ConfigService disponível em toda a aplicação sem ter de reimportar o módulo em todo o lado. O TypeOrmModule.forRootAsync constrói a ligação à base de dados de forma assíncrona, lendo os valores do ConfigService em vez de um objeto estático — necessário para poder usar variáveis de ambiente. O synchronize: true faz com que o TypeORM crie/atualize automaticamente as tabelas com base nas entities: muito prático em desenvolvimento, perigoso em produção (pode apagar dados), onde se usarão migrations.

3. Entities com TypeORM

// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, OneToMany } from 'typeorm';
import { Post } from '../../posts/entities/post.entity';
import { Exclude } from 'class-transformer';

export enum UserRole {
  ADMIN = 'admin',
  AUTHOR = 'author',
  READER = 'reader',
}

@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  email: string;

  @Column()
  @Exclude() // exclui o hash da password das respostas serializadas
  passwordHash: string;

  @Column({ type: 'enum', enum: UserRole, default: UserRole.READER })
  role: UserRole;

  @OneToMany(() => Post, (post) => post.author)
  posts: Post[];

  @CreateDateColumn()
  createdAt: Date;
}
// src/posts/entities/post.entity.ts
import {
  Entity, Column, PrimaryGeneratedColumn, ManyToOne,
  CreateDateColumn, UpdateDateColumn, Index,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';

@Entity('posts')
export class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Index({ unique: true })
  @Column()
  slug: string;

  @Column('text')
  content: string;

  @Column({ default: false })
  published: boolean;

  @Column({ nullable: true })
  coverImageUrl?: string;

  @ManyToOne(() => User, (user) => user.posts, { eager: true })
  author: User;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

4. DTOs com class-validator

// src/posts/dto/create-post.dto.ts
import { IsString, IsBoolean, IsOptional, MinLength, MaxLength } from 'class-validator';

export class CreatePostDto {
  @IsString()
  @MinLength(5)
  @MaxLength(200)
  title: string;

  @IsString()
  @MinLength(20)
  content: string;

  @IsBoolean()
  @IsOptional()
  published?: boolean;
}

// src/posts/dto/update-post.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreatePostDto } from './create-post.dto';

export class UpdatePostDto extends PartialType(CreatePostDto) {}

O PartialType gera automaticamente uma versão em que todos os campos do DTO original se tornam opcionais — perfeito para updates parciais (PATCH), sem duplicar decoradores de validação.

5. Ativar o ValidationPipe Globalmente

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(helmet());
  app.enableCors({ origin: process.env.CORS_ORIGIN?.split(',') ?? '*' });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,           // remove propriedades não presentes no DTO
      forbidNonWhitelisted: true, // lança erro se chegarem propriedades extra
      transform: true,            // converte automaticamente os tipos (ex. string → number)
    }),
  );

  app.useGlobalFilters(new HttpExceptionFilter());
  app.setGlobalPrefix('api/v1');

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

whitelist: true + forbidNonWhitelisted: true são o par mais importante para a segurança dos DTOs: sem isso, um cliente poderia enviar campos extra (ex. role: 'admin' num pedido de registo) que o TypeORM poderia inadvertidamente persistir se o código não os filtrar explicitamente noutro lugar.

6. Middleware Personalizado

// src/common/middleware/request-id.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';

@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction): void {
    req['requestId'] = randomUUID();
    res.setHeader('X-Request-Id', req['requestId']);
    next();
  }
}

// registo em app.module.ts
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(RequestIdMiddleware).forRoutes('*');
  }
}

7. Guards: JwtAuthGuard e RolesGuard

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

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

  async validate(payload: { sub: string; email: string; role: string }) {
    // O valor devolvido é anexado a req.user
    return { userId: payload.sub, email: payload.email, role: payload.role };
  }
}
// src/common/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { UserRole } from '../../users/entities/user.entity';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);

// src/common/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
import { UserRole } from '../../users/entities/user.entity';

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

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) return true; // sem restrição de role nesta rota

    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.includes(user?.role);
  }
}

O padrão Reflector.getAllAndOverride lê os metadados definidos pelo decorador @Roles(...) tanto a nível de método individual como de classe inteira, permitindo definir uma restrição de role num controller inteiro e substituí-la em rotas individuais quando necessário.

8. Interceptors: Logging e Transformação da Resposta

// src/common/interceptors/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger('HTTP');

  intercept(context: ExecutionContext, next: CallHandler): Observable {
    const req = context.switchToHttp().getRequest();
    const start = Date.now();

    return next.handle().pipe(
      tap(() => {
        const ms = Date.now() - start;
        this.logger.log(`${req.method} ${req.url} — ${ms}ms`);
      }),
    );
  }
}

// src/common/interceptors/transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface Response {
  success: true;
  data: T;
  timestamp: string;
}

@Injectable()
export class TransformInterceptor implements NestInterceptor> {
  intercept(context: ExecutionContext, next: CallHandler): Observable> {
    return next.handle().pipe(
      map((data) => ({
        success: true,
        data,
        timestamp: new Date().toISOString(),
      })),
    );
  }
}

9. Exception Filter Global

// src/common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger('ExceptionFilter');

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

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

    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Errore interno del server';

    // Regista o stack trace completo apenas no servidor, nunca na resposta ao cliente
    if (!(exception instanceof HttpException)) {
      this.logger.error(exception instanceof Error ? exception.stack : exception);
    }

    response.status(status).json({
      success: false,
      statusCode: status,
      path: request.url,
      timestamp: new Date().toISOString(),
      message,
    });
  }
}

Um detalhe de segurança crucial: quando a exceção não é uma HttpException conhecida (ou seja, um erro inesperado, ex. um bug ou um erro da base de dados), a mensagem devolvida ao cliente é genérica ("Erro interno do servidor") — o stack trace real é apenas registado no servidor. Expor stack traces aos clientes é um problema de segurança conhecido (information disclosure).

10. Pipe Personalizado

// src/common/pipes/parse-uuid-or-404.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, NotFoundException } from '@nestjs/common';
import { isUUID } from 'class-validator';

@Injectable()
export class ParseUuidOr404Pipe implements PipeTransform {
  transform(value: string, _metadata: ArgumentMetadata): string {
    if (!isUUID(value)) {
      throw new NotFoundException(`Risorsa con id "${value}" non trovata`);
    }
    return value;
  }
}

Devolver um 404 em vez de um genérico 400 Bad Request quando o id nem sequer é um UUID válido é uma escolha deliberada: do ponto de vista do cliente, "recurso não encontrado" é semanticamente mais correto e não revela detalhes sobre o formato interno dos ids.

Exemplo Real: API de Blog Completa

Vamos juntar todas as peças num módulo PostsModule completo, com CRUD, autenticação, autorização baseada em roles e upload da imagem de capa.

Auth Service: login, register, refresh token

// src/auth/auth.service.ts
import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt';
import { UsersService } from '../users/users.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
    private config: ConfigService,
  ) {}

  async register(dto: RegisterDto) {
    const existing = await this.usersService.findByEmail(dto.email);
    if (existing) throw new ConflictException('Email già registrata');

    const passwordHash = await bcrypt.hash(dto.password, 12);
    const user = await this.usersService.create({ ...dto, passwordHash });
    return this.buildTokens(user.id, user.email, user.role);
  }

  async login(dto: LoginDto) {
    const user = await this.usersService.findByEmail(dto.email);
    if (!user) throw new UnauthorizedException('Credenziali non valide');

    const passwordValid = await bcrypt.compare(dto.password, user.passwordHash);
    if (!passwordValid) throw new UnauthorizedException('Credenziali non valide');

    return this.buildTokens(user.id, user.email, user.role);
  }

  async refresh(userId: string, email: string, role: string) {
    // Em produção: verifica também se o refresh token ainda é válido no servidor
    // (whitelist/blacklist) para poder revogá-lo, ex. no logout.
    return this.buildTokens(userId, email, role);
  }

  private buildTokens(sub: string, email: string, role: string) {
    const payload = { sub, email, role };
    const accessToken = this.jwtService.sign(payload, {
      secret: this.config.get('jwt.accessSecret'),
      expiresIn: this.config.get('jwt.accessExpiresIn'),
    });
    const refreshToken = this.jwtService.sign(payload, {
      secret: this.config.get('jwt.refreshSecret'),
      expiresIn: this.config.get('jwt.refreshExpiresIn'),
    });
    return { accessToken, refreshToken };
  }
}

Porque dois tokens separados? O access token tem uma vida curta (15 minutos) e é o que é enviado em cada pedido — se for roubado, o dano é limitado no tempo. O refresh token tem uma vida mais longa (dias) mas só é usado para obter um novo access token num endpoint dedicado, reduzindo a superfície de ataque.

Auth Controller

// src/auth/auth.controller.ts
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('register')
  register(@Body() dto: RegisterDto) {
    return this.authService.register(dto);
  }

  @Post('login')
  login(@Body() dto: LoginDto) {
    return this.authService.login(dto);
  }

  @UseGuards(AuthGuard('jwt-refresh'))
  @Post('refresh')
  refresh(@Req() req) {
    return this.authService.refresh(req.user.userId, req.user.email, req.user.role);
  }
}

Posts Controller: CRUD, RBAC e Upload

// src/posts/posts.controller.ts
import {
  Controller, Get, Post, Body, Patch, Param, Delete,
  UseGuards, UseInterceptors, UploadedFile, Query,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AuthGuard } from '@nestjs/passport';
import { PostsService } from './posts.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { Roles } from '../common/decorators/roles.decorator';
import { RolesGuard } from '../common/guards/roles.guard';
import { UserRole } from '../users/entities/user.entity';
import { ParseUuidOr404Pipe } from '../common/pipes/parse-uuid-or-404.pipe';
import { CurrentUser } from '../common/decorators/current-user.decorator';

@Controller('posts')
export class PostsController {
  constructor(private postsService: PostsService) {}

  @Get()
  findAll(@Query('page') page = 1, @Query('limit') limit = 10) {
    return this.postsService.findAll(Number(page), Number(limit));
  }

  @Get(':id')
  findOne(@Param('id', ParseUuidOr404Pipe) id: string) {
    return this.postsService.findOne(id);
  }

  @UseGuards(AuthGuard('jwt'), RolesGuard)
  @Roles(UserRole.AUTHOR, UserRole.ADMIN)
  @Post()
  create(@Body() dto: CreatePostDto, @CurrentUser() user) {
    return this.postsService.create(dto, user.userId);
  }

  @UseGuards(AuthGuard('jwt'), RolesGuard)
  @Roles(UserRole.AUTHOR, UserRole.ADMIN)
  @Patch(':id')
  update(@Param('id', ParseUuidOr404Pipe) id: string, @Body() dto: UpdatePostDto, @CurrentUser() user) {
    return this.postsService.update(id, dto, user.userId, user.role);
  }

  @UseGuards(AuthGuard('jwt'), RolesGuard)
  @Roles(UserRole.ADMIN)
  @Delete(':id')
  remove(@Param('id', ParseUuidOr404Pipe) id: string) {
    return this.postsService.remove(id);
  }

  @UseGuards(AuthGuard('jwt'))
  @Post(':id/cover')
  @UseInterceptors(FileInterceptor('file', {
    limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
    fileFilter: (_req, file, cb) => {
      const allowed = ['image/jpeg', 'image/png', 'image/webp'];
      cb(null, allowed.includes(file.mimetype));
    },
  }))
  uploadCover(@Param('id', ParseUuidOr404Pipe) id: string, @UploadedFile() file: Express.Multer.File) {
    return this.postsService.setCoverImage(id, file);
  }
}

Repara como cada rota aplica apenas os guards e as roles estritamente necessárias: findAll/findOne são públicas (sem guard), create/update exigem a role author ou admin, enquanto remove é reservada apenas ao admin — um exemplo concreto do princípio do menor privilégio aplicado rota a rota.

Posts Service

// src/posts/posts.service.ts
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './entities/post.entity';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { UserRole } from '../users/entities/user.entity';
import slugify from 'slugify';

@Injectable()
export class PostsService {
  constructor(@InjectRepository(Post) private postsRepo: Repository) {}

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

  async findOne(id: string): Promise {
    const post = await this.postsRepo.findOne({ where: { id } });
    if (!post) throw new NotFoundException(`Post ${id} non trovato`);
    return post;
  }

  async create(dto: CreatePostDto, authorId: string): Promise {
    const post = this.postsRepo.create({
      ...dto,
      slug: slugify(dto.title, { lower: true, strict: true }),
      author: { id: authorId } as any,
    });
    return this.postsRepo.save(post);
  }

  async update(id: string, dto: UpdatePostDto, userId: string, userRole: UserRole): Promise {
    const post = await this.findOne(id);

    // Um author só pode editar os seus próprios posts; o admin pode editar todos
    if (userRole !== UserRole.ADMIN && post.author.id !== userId) {
      throw new ForbiddenException('Non puoi modificare un post di un altro autore');
    }

    Object.assign(post, dto);
    return this.postsRepo.save(post);
  }

  async remove(id: string): Promise {
    const result = await this.postsRepo.delete(id);
    if (result.affected === 0) throw new NotFoundException(`Post ${id} non trovato`);
  }

  async setCoverImage(id: string, file: Express.Multer.File): Promise {
    const post = await this.findOne(id);
    // Em produção: faz upload para S3/Cloud Storage em vez de disco local
    post.coverImageUrl = `/uploads/${file.filename}`;
    return this.postsRepo.save(post);
  }
}

Repara na verificação userRole !== UserRole.ADMIN && post.author.id !== userId no método update: é um exemplo de autorização ao nível do recurso (não apenas da rota) — um author autenticado com um token JWT válido pode ainda assim ser bloqueado se estiver a tentar modificar o recurso de outra pessoa. Esta verificação deve sempre ser feita no service, nunca delegada apenas ao guard, que opera a um nível demasiado genérico para conhecer o proprietário de um recurso específico.

Boas Práticas

PrincípioAplicação Prática no NestJS
Clean ArchitectureSeparação clara entre controller (HTTP), service (lógica de negócio) e repository (persistência). O domínio nunca deve depender de detalhes de infraestrutura como Express ou TypeORM.
SOLID — Single ResponsibilityUm controller mapeia pedidos HTTP, um service contém uma única área de lógica de negócio, um repository um único aggregate.
SOLID — Dependency InversionOs services dependem de interfaces/tokens abstratos (ex. @InjectRepository), não de implementações concretas — substituíveis por mocks nos testes.
DRYLógica de validação partilhada nos DTOs com PartialType/PickType/OmitType, evitando duplicação entre DTO de Create e Update.
KISSEvita introduzir CQRS, Event Sourcing ou microsserviços até que a complexidade real do domínio o justifique.
Repository PatternO TypeORM/Prisma já fornecem esta camada; evita "furar" a abstração chamando queries SQL em bruto diretamente nos services, exceto em casos críticos de performance.
DTOCada endpoint tem um DTO de entrada dedicado — nunca expor diretamente as entities da base de dados como contrato de API.
ValidationValidationPipe global com whitelist e forbidNonWhitelisted sempre ativos em todos os projetos, sem exceções.

Performance

Caching

// Caching ao nível do endpoint com @nestjs/cache-manager
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
import { UseInterceptors } from '@nestjs/common';

@UseInterceptors(CacheInterceptor)
@CacheTTL(60) // cache por 60 segundos
@Get()
findAll() {
  return this.postsService.findAll(1, 10);
}

Lazy Loading de Módulos

Em aplicações monolíticas de grande dimensão, o LazyModuleLoader permite carregar módulos pouco usados (ex. um módulo de relatórios) apenas quando efetivamente necessários, reduzindo o tempo de bootstrap.

Queries Otimizadas

  • Usa select explícito no TypeORM para evitar transferir colunas desnecessárias (ex. excluir content nas listas).
  • Adiciona índices às colunas usadas frequentemente em WHERE/ORDER BY (vê o @Index no slug na entity Post).
  • Evita o problema N+1: usa relations ou QueryBuilder com leftJoinAndSelect em vez de carregar relações dentro de um loop.
  • Usa sempre paginação (skip/take) — nunca devolvas coleções ilimitadas.

Logging e Monitorização

Em produção, substitui o logger predefinido por nestjs-pino para logs JSON estruturados, facilmente indexáveis por ferramentas como Grafana Loki ou Datadog. Para a monitorização de performance, integra o @nestjs/terminus para health checks (/health) usados por load balancers e orquestradores (Kubernetes, Railway).

Profiling

Para identificar gargalos reais, usa node --prof ou ferramentas APM (Application Performance Monitoring) como New Relic ou Elastic APM, que rastreiam o tempo gasto em cada handler, query e chamada externa — evita otimizar "por sensação" sem dados reais.

Segurança

MedidaComo Implementar
JWTAccess token de vida curta assinado com um secret robusto (32+ caracteres aleatórios), nunca hardcoded no código.
Refresh TokenVida mais longa, idealmente com possibilidade de revogação (whitelist em Redis/DB), rotação em cada uso.
Hash de Password / bcryptbcrypt.hash(password, 12) — nunca MD5/SHA1, nunca passwords em texto simples na BD.
Helmetapp.use(helmet()) define headers de segurança (CSP, X-Frame-Options, HSTS) numa única linha.
CORSapp.enableCors({ origin: [...] }) com whitelist explícita de domínios, nunca origin: '*' em produção se a API exigir credentials.
Rate Limiting@nestjs/throttler para limitar pedidos por IP, essencial em endpoints como /auth/login para mitigar ataques de força bruta.
ValidationPipewhitelist + forbidNonWhitelisted para bloquear mass assignment em campos não previstos.
Sanitização de Inputclass-validator para a estrutura, mais sanitização explícita de HTML (ex. sanitize-html) se o campo permitir markup livre, para prevenir XSS.
// Exemplo: rate limiting mais agressivo especificamente no login
import { Throttle } from '@nestjs/throttler';

@Throttle({ default: { limit: 5, ttl: 60000 } }) // máx. 5 tentativas por minuto por IP
@Post('login')
login(@Body() dto: LoginDto) {
  return this.authService.login(dto);
}

Erros Comuns

1. Esquecer whitelist/forbidNonWhitelisted no ValidationPipe

Problema: um cliente pode enviar campos extra não previstos pelo DTO (ex. role: 'admin').
Causa: ValidationPipe configurado sem whitelist: true.
Solução: ativa sempre whitelist e forbidNonWhitelisted globalmente em main.ts.

2. synchronize: true em Produção

Problema: o TypeORM pode alterar/apagar colunas ou tabelas em runtime.
Causa: confusão entre o ambiente de desenvolvimento e produção na configuração da base de dados.
Solução: synchronize: false em produção, gere o schema com migrations explícitas (typeorm migration:generate).

3. Dependência Circular Entre Módulos

Problema: erro "Nest cannot resolve dependencies" ou crash silencioso no arranque.
Causa: dois módulos importam-se mutuamente de forma direta.
Solução: usa forwardRef(() => ModuleB) em ambos os módulos, ou — melhor ainda — extrai a dependência partilhada para um terceiro módulo.

4. Expor as Entities Diretamente como Resposta da API

Problema: campos sensíveis (ex. passwordHash) acabam na resposta JSON.
Causa: o controller devolve a entity TypeORM tal como está, sem uma camada DTO/serializer.
Solução: usa o @Exclude() do class-transformer na entity juntamente com um ClassSerializerInterceptor global, ou melhor ainda um DTO de resposta explícito.

5. Passwords Guardadas Sem Hashing

Problema: um comprometimento da BD expõe todas as passwords em texto simples.
Causa: omissão (frequentemente durante o rapid prototyping) do passo de hashing.
Solução: hash com bcrypt/argon2 sempre, mesmo em desenvolvimento — nunca há uma razão válida para o saltar.

6. Secrets JWT Fracos ou Hardcoded no Código

Problema: um secret previsível permite falsificar tokens válidos.
Causa: um secret predefinido deixado em produção, ou commitado no repositório.
Solução: secrets gerados aleatoriamente (pelo menos 256 bits), geridos apenas através de variáveis de ambiente/secret manager, nunca no código fonte.

7. Falta de Gestão de Erros Assíncronos nos Services

Problema: promise rejections não tratadas provocam crash do processo Node.
Causa: chamadas async sem try/catch onde necessário, ou esquecimento do exception filter global.
Solução: deixa que as exceções subam até ao exception filter global (comportamento predefinido do Nest), intercepta apenas onde é necessário um comportamento diferente (fallback, retry).

8. Queries N+1 Não Detetadas

Problema: o endpoint torna-se extremamente lento com o crescimento dos dados.
Causa: carregamento de relações dentro de um loop em vez de um único join.
Solução: usa relations/leftJoinAndSelect, monitoriza as queries geradas com logging: true em desenvolvimento.

9. CORS Aberto a Todos em Produção com Credentials

Problema: qualquer website pode fazer pedidos autenticados à API em nome do utilizador.
Causa: origin: '*' combinado com credentials: true.
Solução: whitelist explícita dos domínios autorizados.

10. Sem Rate Limiting nos Endpoints de Autenticação

Problema: ataques de força bruta ao login/registo.
Causa: @nestjs/throttler não aplicado, ou com limites demasiado permissivos.
Solução: um @Throttle mais restritivo especificamente no login/registo/reset de password.

11. DTO de Update Idêntico ao DTO de Create Sem PartialType

Problema: cada PATCH exige todos os campos obrigatórios, quebrando os updates parciais.
Causa: o DTO de Create é copiado sem tornar os campos opcionais.
Solução: class UpdateXDto extends PartialType(CreateXDto) {}.

12. Guards Aplicados Apenas ao Nível da Rota, Nunca do Recurso

Problema: um utilizador autenticado pode modificar recursos de outros utilizadores.
Causa: confia-se apenas no @UseGuards(AuthGuard('jwt')) sem verificação de ownership no service.
Solução: verificação explícita resource.ownerId === user.id (ou role admin) no service, como mostrado no exemplo PostsService.update.

13. Ligação à Base de Dados Sem Pool Configurado Corretamente

Problema: esgotamento de ligações sob carga, erros "too many clients".
Causa: valores predefinidos do pool não adequados ao tráfego real.
Solução: configura explicitamente extra: { max: 20 } na connection do TypeORM, calibrado com o plano da base de dados.

14. Upload de Ficheiros Sem Limites de Tamanho ou Tipo

Problema: um utilizador malicioso carrega ficheiros enormes ou executáveis disfarçados de imagens.
Causa: FileInterceptor configurado sem limits nem fileFilter.
Solução: define sempre limits.fileSize e valida o mimetype, como no exemplo uploadCover.

15. Variáveis de Ambiente Não Validadas no Arranque

Problema: a app arranca "silenciosamente" com configuração incompleta e falha de forma críptica mais tarde.
Causa: nenhum schema de validação sobre as env vars.
Solução: usa ConfigModule.forRoot({ validationSchema: Joi.object({...}) }) para falhar o bootstrap imediatamente se faltar uma variável crítica.

16. Logging de Dados Sensíveis

Problema: passwords, tokens ou dados pessoais acabam nos logs em texto simples.
Causa: logging indiscriminado de todo o body do pedido.
Solução: redige (mascara) explicitamente os campos sensíveis antes do logging, ou usa os redact paths do pino.

17. Testes Escritos Apenas para o "Happy Path"

Problema: bugs que só surgem com input inválido ou edge cases permanecem invisíveis até à produção.
Causa: pressão de tempo, cobertura de testes limitada ao fluxo principal.
Solução: testa explicitamente os erros 4xx esperados (validação, permissões, not found), não apenas os 2xx.

18. Ausência de Versionamento da API

Problema: cada alteração breaking quebra os clientes existentes sem aviso.
Causa: nenhum prefixo de versão (/api/v1) ou estratégia de versionamento do Nest configurada.
Solução: app.setGlobalPrefix('api/v1') desde o primeiro dia, mesmo em projetos pequenos.

19. Dependências Desatualizadas com Vulnerabilidades Conhecidas

Problema: a API permanece exposta a CVEs publicamente documentadas.
Causa: falta de um processo de auditoria periódica das dependências.
Solução: npm audit integrado na CI, atualizações regulares com Dependabot/Renovate.

20. Nenhuma Distinção Entre Erros de Validação e Erros de Domínio

Problema: todos os erros voltam como 500 genérico, tornando impossível para o cliente distinguir um input errado de um problema do servidor.
Causa: uso de Error genérico em vez das classes HttpException específicas do Nest.
Solução: usa sempre as exceções tipadas corretas (BadRequestException, NotFoundException, ForbiddenException, ConflictException...) — é assim que o Nest comunica o status code HTTP correto.

FAQ

1. O NestJS é adequado também para projetos pequenos?

Sim, mas o verdadeiro valor surge quando o projeto cresce: num script pequeno, a estrutura pode parecer excessiva em comparação com o Express puro.

2. Tenho de usar TypeScript obrigatoriamente?

Tecnicamente o NestJS também suporta JavaScript puro, mas perde-se grande parte do valor (decoradores tipados, DI type-safe). É fortemente desaconselhado em produção.

3. TypeORM ou Prisma?

O TypeORM integra-se de forma mais nativa com os decoradores do NestJS e é mais maduro no ecossistema Nest; o Prisma oferece um client gerado mais type-safe e uma melhor developer experience nas migrations, mas requer uma camada de integração manual com o Nest.

4. Como faço a gestão das migrations da base de dados?

Com o TypeORM: typeorm migration:generate para as gerar a partir do diff das entities, typeorm migration:run para as aplicar em CI/CD, nunca synchronize: true em produção.

5. Como estruturo um projeto muito grande?

Modulariza por domínio (feature modules), não por tipo técnico — evita pastas globais "controllers/", "services/" que misturam domínios diferentes.

6. Como faço testing dos controllers?

Com Test.createTestingModule do @nestjs/testing, mockando os services injetados; para os services, mocka os repositories TypeORM com getRepositoryToken.

7. Qual é a diferença entre Guard e Middleware para autenticação?

O middleware não tem acesso ao contexto de execução do Nest (ex. metadados dos decoradores), logo não consegue ler @Roles(). Os guards conseguem — por isso a autenticação/autorização vai sempre nos guards, nunca nos middlewares.

8. Posso usar GraphQL em vez de REST?

Sim, o NestJS tem suporte oficial tanto para Apollo Server como para Mercurius, com o mesmo sistema de módulos/DI — útil se precisares de queries flexíveis do lado do cliente.

9. Como faço a gestão de transações na base de dados?

Com DataSource.transaction() do TypeORM, ou com o decorador @Transactional() da biblioteca typeorm-transactional para uma sintaxe mais declarativa.

10. Como implemento o refresh token de forma segura?

Guarda um hash do refresh token no servidor (whitelist), roda-o a cada uso (token rotation), e invalida-o explicitamente no logout.

11. O NestJS suporta microsserviços?

Sim, nativamente, com transport layers para TCP, Redis, RabbitMQ, Kafka, gRPC e NATS através do @nestjs/microservices.

12. Como faço deploy para produção?

Build com nest build (compila para dist/), depois node dist/main.js; em plataformas como Railway/Render/Fly.io basta um Dockerfile multi-stage ou o buildpack nativo do Node.

13. Como faço a gestão de variáveis de ambiente diferentes para dev/staging/prod?

ConfigModule.forRoot({ envFilePath: ['.env.${NODE_ENV}', '.env'] }), com os valores reais injetados pela plataforma de hosting em produção, não a partir de ficheiros .env commitados.

14. Como implemento upload para S3 em vez de disco local?

Substitui o storage engine do Multer por multer-s3, ou faz o upload manualmente no service com o SDK da AWS depois de receber o buffer em memória.

15. Como documento automaticamente a API?

Com o @nestjs/swagger: decoradores @ApiTags, @ApiProperty nos DTOs, e SwaggerModule.setup() em main.ts geram uma UI OpenAPI interativa em /api/docs.

16. Como implemento soft delete?

O TypeORM suporta nativamente @DeleteDateColumn() na entity: softDelete() define a coluna em vez de remover a linha, e as queries excluem automaticamente os registos eliminados.

17. Como faço a gestão de múltiplas versões da API em simultâneo?

Com app.enableVersioning({ type: VersioningType.URI }) e o decorador @Version('2') nos controllers/métodos individuais que precisam de coexistir com a v1.

18. É necessário um ORM ou posso usar queries SQL diretas?

Para a maioria dos casos um ORM reduz erros e boilerplate; para queries muito complexas ou críticas em performance, o TypeORM permite mesmo assim queries raw através do QueryRunner, mantendo o resto da app no ORM.

19. Como implemento WebSocket no NestJS?

Com @nestjs/websockets e um @WebSocketGateway(), que se integra com Socket.IO ou ws mantendo o mesmo sistema de DI e guards das rotas REST.

20. Como faço a gestão de cron jobs?

Com @nestjs/schedule e o decorador @Cron('0 0 * * *') num método de um service — útil para limpeza de dados, envio de digests por email, sincronizações periódicas.

21. Como protejo a API contra ataques de SQL Injection?

O TypeORM/Prisma parametrizam automaticamente as queries — o risco só surge se construíres queries raw concatenando strings manualmente, algo que deve ser sempre evitado.

22. Qual é a forma correta de gerir secrets em produção?

Nunca no código ou em ficheiros commitados: usa as variáveis de ambiente da plataforma de hosting ou um secret manager dedicado (AWS Secrets Manager, Doppler, Railway Variables).

23. Como escalo horizontalmente uma API NestJS?

A app deve ser stateless (sem sessões locais em memória — usa JWT ou Redis para o estado partilhado), depois é replicada atrás de um load balancer; para WebSocket é necessário um adapter Redis para sincronizar as ligações entre instâncias.

24. Como faço a gestão da retrocompatibilidade quando altero um schema de resposta?

Introduz um novo campo em vez de renomear o existente, deprecia-o gradualmente com documentação clara, e usa o versionamento da API para alterações realmente breaking.

25. Vale a pena escrever testes E2E além dos unit tests para uma API NestJS?

Sim — o @nestjs/testing com supertest permite testar toda a pipeline (incluindo guards, pipes, interceptors) chamando os endpoints reais numa instância da app em memória, apanhando problemas de integração que os unit tests sozinhos não veem.

Recursos e Documentação Oficial

Para aprofundar cada tema abordado neste guia, a documentação oficial continua a ser a fonte mais fiável e atualizada:

  • NestJS — documentação oficial: docs.nestjs.com, em particular as secções sobre Guards, Interceptors, Pipes e Exception Filters.
  • TypeORM — documentação oficial: typeorm.io, para relações, migrations e o query builder avançado.
  • Passport.js: passportjs.org, a biblioteca de autenticação em que o @nestjs/passport se baseia.
  • class-validator: repositório oficial no GitHub, com a lista completa dos decoradores de validação disponíveis para além dos usados neste guia.
  • OWASP API Security Top 10: a checklist de referência para a segurança de APIs REST, complementar à secção de Segurança deste guia.
  • jwt.io: ferramenta para inspecionar e descodificar tokens JWT durante o debug da autenticação.

Artigos Relacionados Recomendados

Este guia cobre todo o ciclo de vida de uma REST API NestJS, mas cada secção pode tornar-se um aprofundamento à parte. Eis os temas naturalmente relacionados que vale a pena explorar a seguir:

  • Modularizar um projeto NestJS de grande dimensão: feature modules, shared modules e barrel files.
  • Dependency Injection no NestJS explicada com exemplos: providers customizados, factory providers, tokens de injeção.
  • Autenticação JWT e Refresh Token em profundidade: token rotation, revogação e gestão multi-dispositivo.
  • Role Based Access Control (RBAC) avançado: permissões granulares além dos simples roles.
  • Validação com ValidationPipe e class-validator: validadores customizados e mensagens de erro localizadas.
  • Logging centralizado no NestJS com Pino e correlação de pedidos através de request ID.
  • Exception Filters personalizados para domínios específicos (ex. erros de pagamento, erros de domínio).
  • Upload de ficheiros avançado com Multer: storage em S3, validação do conteúdo real dos ficheiros.
  • Scheduler e Cron Jobs no NestJS com @nestjs/schedule.
  • WebSocket com NestJS: notificações em tempo real e scaling com o Redis adapter.
  • Testing de controllers e services NestJS: unit tests, mock de repositories, testes E2E com Supertest.

Conclusão

Construir uma REST API com NestJS significa investir numa estrutura que escala com a complexidade do projeto: módulos bem isolados, Dependency Injection para testabilidade, guards/interceptors/pipes para separar cross-cutting concerns da lógica de negócio, e um ecossistema — TypeORM, Passport, class-validator, Swagger — que cobre de forma madura todas as necessidades reais de produção. O exemplo de API de Blog construído neste guia (CRUD, JWT com refresh token, RBAC, upload de ficheiros, logging centralizado, gestão de erros) é o esqueleto reutilizável para a maioria das APIs NestJS que irás encontrar na prática.

Próximos passos recomendados: aprofunda a modularização de projetos NestJS de grande dimensão, a autenticação JWT e refresh token em detalhe, o padrão RBAC com permissões granulares, e as técnicas de testing para controllers e services — temas que completam naturalmente este guia.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!