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

Creare una REST API completa con Nestjs: Guida definitiva a Typeorm, JWT, RBAC e sicurezza

Introduzione

NestJS è oggi il framework backend Node.js più usato per costruire applicazioni server-side scalabili, tipizzate e manutenibili. Combina i concetti più solidi dell'ingegneria del software — Dependency Injection, moduli, architettura a strati — con la produttività di TypeScript e un ecosistema che copre praticamente ogni esigenza: REST, GraphQL, WebSocket, microservizi, CLI, cron job.

In questa guida costruiamo, passo per passo, una REST API completa e pronta per la produzione: un'API Blog con autenticazione JWT, refresh token, controllo degli accessi basato sui ruoli (RBAC), validazione, upload di file, logging centralizzato e gestione strutturata degli errori — esattamente lo stack che si trova dietro la maggior parte delle API NestJS reali nel 2026.

Cos'è NestJS

NestJS è un framework opinionated costruito sopra Express (o, in alternativa, Fastify) che impone una struttura precisa all'applicazione: ogni funzionalità è organizzata in moduli, ogni modulo espone controller (gestiscono le richieste HTTP) e provider (contengono la logica di business, tipicamente service), collegati tra loro tramite un sistema di Dependency Injection integrato e ispirato ad Angular.

Perché usarlo

  • TypeScript nativo: tipizzazione end-to-end, autocompletamento, refactoring sicuro.
  • Architettura imposta: a differenza di Express puro, NestJS obbliga a separare le responsabilità (controller/service/repository), riducendo il "big ball of mud" tipico dei progetti Node cresciuti senza struttura.
  • Dependency Injection integrata: testabilità elevata, basso accoppiamento, provider intercambiabili (utile per mock nei test).
  • Ecosistema maturo: moduli ufficiali per TypeORM, Prisma, Mongoose, Passport, GraphQL, WebSocket, Bull/BullMQ, Swagger, gRPC, microservizi.
  • Decoratori dichiarativi: guard, interceptor, pipe ed exception filter permettono cross-cutting concerns (auth, logging, validazione) senza inquinare la logica di business.

Quando conviene (e quando no)

NestJS conviene quando il progetto ha una complessità che giustifica una struttura rigida: team di più sviluppatori, API destinate a crescere nel tempo, necessità di testabilità elevata, applicazioni enterprise con requisiti di sicurezza e compliance. È probabilmente eccessivo per uno script one-off o un prototipo che verrà buttato via in una settimana — lì Express puro o Fastify "nudo" restano più rapidi da avviare.

Vantaggi e Svantaggi

VantaggiSvantaggi
Struttura chiara e consistente tra progetti diversiCurva di apprendimento più ripida di Express puro (decoratori, DI, moduli)
Testabilità nativa grazie alla DIOverhead di boilerplate per progetti molto piccoli
Ecosistema ufficiale ampio e ben mantenutoMaggiore "magia" (decoratori, reflection) rispetto a codice esplicito
Ottima integrazione con TypeScript e Swagger/OpenAPIBundle size e cold start leggermente superiori a framework minimali (rilevante in ambienti serverless)
Facilita l'adozione di Clean Architecture / hexagonalRichiede disciplina del team per non abusare della flessibilità dei moduli

Casi Reali

NestJS è usato in produzione da aziende come Adidas, Roche, Autodesk e Decathlon, oltre a essere una scelta molto comune per backend SaaS B2B, piattaforme e-commerce, sistemi di gestione utenti e API di microservizi che devono comunicare tra loro tramite gRPC o code di messaggi (RabbitMQ, Kafka).

Prerequisiti

Prima di iniziare, assicurati di avere questi strumenti installati e di conoscere le basi di TypeScript (interfacce, decoratori, generics) e dei concetti REST (verbi HTTP, status code, idempotenza).

StrumentoVersione consigliataA cosa serve
Node.js20.x LTS o superioreRuntime JavaScript su cui gira NestJS
npm10.x (incluso in Node 20)Gestione dei pacchetti
NestJS CLI@nestjs/cli 10.x+Scaffolding di moduli, controller, service
TypeScript5.xLinguaggio in cui è scritto NestJS e la tua app
VS CodeUltima stabileEditor con supporto TypeScript nativo
PostgreSQL15.x o superioreDatabase relazionale per l'esempio con TypeORM
Docker (opzionale)24.x+Avviare PostgreSQL localmente senza installazione nativa
# Verifica le versioni installate
node -v
npm -v

# Installa la CLI di NestJS globalmente
npm install -g @nestjs/cli

nest --version

Suggerimento: se non vuoi installare PostgreSQL nativamente, avvialo con Docker: docker run --name pg-blog -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=blog_db -p 5432:5432 -d postgres:16.

Architettura

Prima di scrivere codice, è fondamentale capire come NestJS instrada una richiesta HTTP attraverso i suoi componenti principali. Ecco il ciclo di vita completo di una richiesta:

Client
  │
  ▼
┌─────────────┐
│  Middleware  │  (es. logger, cookie-parser — eseguito prima del routing)
└──────┬───────┘
       ▼
┌─────────────┐
│    Guard     │  (es. AuthGuard — decide se la richiesta può procedere)
└──────┬───────┘
       ▼
┌─────────────┐
│ Interceptor  │  (fase "prima" — es. logging, trasformazione input)
└──────┬───────┘
       ▼
┌─────────────┐
│     Pipe     │  (validazione e trasformazione dei parametri in ingresso)
└──────┬───────┘
       ▼
┌─────────────┐
│  Controller  │  (riceve la richiesta, delega al service)
└──────┬───────┘
       ▼
┌─────────────┐
│   Service    │  (logica di business, chiama i repository)
└──────┬───────┘
       ▼
┌─────────────┐
│ Repository / │  (accesso ai dati — TypeORM, Prisma, Mongoose...)
│   Database   │
└──────┬───────┘
       ▼
┌─────────────┐
│ Interceptor  │  (fase "dopo" — es. trasformazione della risposta)
└──────┬───────┘
       ▼
┌──────────────────┐
│ Exception Filter  │  (intercetta SOLO se viene lanciata un'eccezione)
└──────┬────────────┘
       ▼
    Response

Controller

Il livello più esterno: riceve le richieste HTTP, estrae parametri/body/query e delega immediatamente la logica al service corrispondente. Un controller non deve mai contenere logica di business — la sua unica responsabilità è il mapping HTTP ↔ chiamata di metodo.

Service

Contiene la logica di business vera e propria. È un provider iniettabile, tipicamente marcato con @Injectable(), e viene iniettato nel controller (o in altri service) tramite il costruttore.

Module

Il modulo è l'unità organizzativa di NestJS: raggruppa controller, provider e import di altri moduli. Ogni applicazione ha almeno un AppModule radice, e le funzionalità vengono tipicamente isolate in feature module (es. PostsModule, AuthModule, UsersModule).

Provider

Qualsiasi classe gestita dal contenitore di Dependency Injection di Nest: service, repository, factory, helper. Viene dichiarato in providers nel modulo e può essere iniettato ovunque sia disponibile (nello stesso modulo, o esportato verso altri moduli).

Middleware

Funzioni eseguite prima del routing di Nest, con accesso diretto a req, res e next() — lo stesso modello di Express. Utile per logging grezzo, parsing di cookie, o header custom applicati a livello globale.

Guard

Decidono se una richiesta può procedere, restituendo true/ false (o lanciando un'eccezione). Sono il posto corretto per autenticazione e autorizzazione — mai dentro il controller o il service.

Interceptor

Si posizionano attorno all'esecuzione del gestore della rotta (come un middleware AOP): possono trasformare la richiesta prima che arrivi al controller e la risposta prima che esca. Casi d'uso tipici: logging dei tempi di risposta, trasformazione uniforme delle risposte, caching, gestione di timeout.

Pipe

Trasformano e validano i dati in ingresso (parametri di rotta, query string, body) prima che raggiungano il controller. ValidationPipe, integrato con class-validator, è il pipe più usato in assoluto in ogni API NestJS seria.

Exception Filter

Intercettano le eccezioni lanciate in qualsiasi punto della richiesta e le trasformano in una risposta HTTP consistente (status code, corpo JSON strutturato), evitando che stack trace o errori grezzi raggiungano il client.

Installazione

# 1. Crea un nuovo progetto NestJS
nest new blog-api

# Durante la creazione, scegli npm come package manager quando richiesto

cd blog-api

# 2. Installa le dipendenze per il database (TypeORM + driver PostgreSQL)
npm install @nestjs/typeorm typeorm pg

# 3. Installa le dipendenze per la validazione
npm install class-validator class-transformer

# 4. Installa le dipendenze per l'autenticazione JWT
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install --save-dev @types/passport-jwt @types/bcrypt

# 5. Installa le dipendenze per sicurezza e rate limiting
npm install helmet @nestjs/throttler

# 6. Installa le dipendenze per l'upload di file
npm install @nestjs/platform-express multer
npm install --save-dev @types/multer

# 7. Installa Swagger per la documentazione automatica dell'API
npm install @nestjs/swagger

# 8. Installa il logger strutturato
npm install nestjs-pino pino-http pino-pretty

# 9. Configura le variabili d'ambiente
npm install @nestjs/config

Ogni comando installa un blocco funzionale ben preciso: @nestjs/typeorm + typeorm + pg collegano Nest a PostgreSQL tramite l'ORM TypeORM; class-validator/class-transformer abilitano i DTO validati automaticamente; lo stack passport/passport-jwt/bcrypt costruisce l'intero flusso di autenticazione; helmet e @nestjs/throttler irrobustiscono la sicurezza HTTP e il rate limiting; multer gestisce il multipart/form-data per gli upload; nestjs-pino fornisce logging strutturato JSON, adatto alla produzione.

Implementazione Passo-Passo

1. Struttura delle cartelle

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. Configurazione centralizzata con @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', // ⚠️ solo in sviluppo
      }),
    }),
    ThrottlerModule.forRoot([{ ttl: 60000, limit: 100 }]),
    AuthModule,
    UsersModule,
    PostsModule,
  ],
})
export class AppModule {}

Riga per riga: ConfigModule.forRoot({ isGlobal: true }) rende ConfigService disponibile in tutta l'applicazione senza dover reimportare il modulo ovunque. TypeOrmModule.forRootAsync costruisce la connessione al database in modo asincrono, leggendo i valori dal ConfigService invece che da un oggetto statico — necessario per poter usare variabili d'ambiente. synchronize: true fa sì che TypeORM crei/aggiorni automaticamente le tabelle in base alle entity: comodissimo in sviluppo, pericoloso in produzione (può cancellare dati), dove si useranno invece le migration.

3. Entity con 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() // esclude l'hash della password dalle risposte serializzate
  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. DTO con 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) {}

PartialType genera automaticamente una versione dove tutti i campi del DTO originale diventano opzionali — perfetto per gli update parziali (PATCH), senza duplicare i decoratori di validazione.

5. Abilitare globalmente ValidationPipe

// 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,           // rimuove le proprietà non presenti nel DTO
      forbidNonWhitelisted: true, // lancia errore se arrivano proprietà extra
      transform: true,            // converte automaticamente i tipi (es. string → number)
    }),
  );

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

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

whitelist: true + forbidNonWhitelisted: true sono la coppia più importante per la sicurezza dei DTO: senza di essa, un client potrebbe inviare campi extra (es. role: 'admin' in una richiesta di registrazione) che TypeORM potrebbe inavvertitamente persistere se il codice non li filtra esplicitamente altrove.

6. Middleware custom

// 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();
  }
}

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

7. Guard: 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 }) {
    // Il valore restituito viene allegato 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; // nessun vincolo di ruolo sulla rotta

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

Il pattern Reflector.getAllAndOverride legge i metadati impostati dal decoratore @Roles(...) sia a livello di singolo metodo che di intera classe, permettendo di definire un vincolo di ruolo su un intero controller e sovrascriverlo su singole rotte quando necessario.

8. Interceptor: logging e trasformazione della risposta

// 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 globale

// 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';

    // Logga lo stack trace completo solo lato server, mai nella risposta al client
    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,
    });
  }
}

Un dettaglio di sicurezza cruciale: quando l'eccezione non è una HttpException conosciuta (quindi un errore inatteso, es. un bug o un errore del database), il messaggio restituito al client è generico ("Errore interno del server") — lo stack trace reale viene loggato solo lato server. Esporre stack trace ai client è un problema di sicurezza noto (information disclosure).

10. Pipe custom

// 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;
  }
}

Restituire un 404 invece di un generico 400 Bad Request quando l'id non è nemmeno un UUID valido è una scelta deliberata: dal punto di vista del client, "risorsa non trovata" è semanticamente più corretto e non rivela dettagli sul formato interno degli id.

Esempio Reale: API Blog Completa

Mettiamo insieme tutti i pezzi in un modulo PostsModule completo, con CRUD, autenticazione, autorizzazione basata sui ruoli e upload dell'immagine di copertina.

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) {
    // In produzione: verifica anche che il refresh token sia ancora valido lato DB
    // (whitelist/blacklist) per poterlo revocare, es. al 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 };
  }
}

Perché due token separati? L'access token ha una vita breve (15 minuti) ed è quello inviato ad ogni richiesta — se venisse rubato, il danno è limitato nel tempo. Il refresh token ha una vita più lunga (giorni) ma viene usato solo per ottenere un nuovo access token su un endpoint dedicato, riducendo la superficie di attacco.

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);
  }
}

Nota come ogni rotta applichi solo i guard e i ruoli strettamente necessari: findAll/findOne sono pubbliche (nessun guard), create/ update richiedono ruolo author o admin, mentre remove è riservata al solo admin — un esempio concreto di principio del privilegio minimo applicato rotta per rotta.

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);

    // Un author può modificare solo i propri post; l'admin può modificarli tutti
    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);
    // In produzione: carica su S3/Cloud Storage invece che su disco locale
    post.coverImageUrl = `/uploads/${file.filename}`;
    return this.postsRepo.save(post);
  }
}

Osserva il controllo userRole !== UserRole.ADMIN && post.author.id !== userId nel metodo update: è un esempio di autorizzazione a livello di risorsa (non solo di rotta) — un author autenticato con un token JWT valido può comunque essere bloccato se sta tentando di modificare la risorsa di qualcun altro. Questo controllo va sempre fatto nel service, mai delegato solo al guard, che opera a un livello troppo generico per conoscere il proprietario della singola risorsa.

Best Practice

PrincipioApplicazione pratica in NestJS
Clean ArchitectureSeparazione netta tra controller (HTTP), service (business logic) e repository (persistenza). Il dominio non deve mai dipendere da dettagli infrastrutturali come Express o TypeORM.
SOLID — Single ResponsibilityUn controller mappa richieste HTTP, un service contiene una singola area di business logic, un repository un solo aggregate.
SOLID — Dependency InversionI service dipendono da interfacce/token astratti (es. @InjectRepository), non da implementazioni concrete — sostituibili nei test con mock.
DRYLogica di validazione condivisa nei DTO con PartialType/PickType/OmitType, evitando duplicazione tra Create e Update DTO.
KISSEvita di introdurre CQRS, Event Sourcing o microservizi finché la complessità reale del dominio non lo giustifica.
Repository PatternTypeORM/Prisma già forniscono questo layer; evita di "bucare" l'astrazione chiamando query SQL grezze direttamente nei service salvo casi di performance critica.
DTOOgni endpoint ha un DTO di input dedicato — mai esporre direttamente le entity del database come contratto di API.
ValidationValidationPipe globale con whitelist e forbidNonWhitelisted sempre attivi in ogni progetto, senza eccezioni.

Performance

Caching

// Caching a livello di endpoint con @nestjs/cache-manager
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
import { UseInterceptors } from '@nestjs/common';

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

Lazy Loading dei moduli

In applicazioni monolitiche di grandi dimensioni, LazyModuleLoader permette di caricare moduli poco usati (es. un modulo di reportistica) solo quando effettivamente richiesti, riducendo il tempo di bootstrap.

Query ottimizzate

  • Usa select esplicito in TypeORM per evitare di trasferire colonne non necessarie (es. escludere content nelle liste).
  • Aggiungi indici sulle colonne usate in WHERE/ORDER BY frequenti (vedi @Index sullo slug nell'entity Post).
  • Evita il problema N+1: usa relations o QueryBuilder con leftJoinAndSelect invece di caricare le relazioni in loop.
  • Usa sempre paginazione (skip/take) — non restituire mai collezioni illimitate.

Logging e Monitoraggio

In produzione, sostituisci il logger di default con nestjs-pino per log strutturati JSON, facilmente indicizzabili da strumenti come Grafana Loki o Datadog. Per il monitoraggio delle performance, integra @nestjs/terminus per gli health check (/health) usati da load balancer e orchestratori (Kubernetes, Railway).

Profiling

Per individuare colli di bottiglia reali, usa node --prof o strumenti APM (Application Performance Monitoring) come New Relic o Elastic APM, che tracciano il tempo speso in ogni handler, query e chiamata esterna — evita di ottimizzare "a sensazione" senza dati reali.

Sicurezza

MisuraCome si implementa
JWTAccess token a vita breve firmato con secret robusto (32+ caratteri random), mai hardcoded nel codice.
Refresh TokenVita più lunga, idealmente con possibilità di revoca (whitelist in Redis/DB), rotazione ad ogni uso.
Hash Password / bcryptbcrypt.hash(password, 12) — mai MD5/SHA1, mai password in chiaro nel DB.
Helmetapp.use(helmet()) imposta header di sicurezza (CSP, X-Frame-Options, HSTS) con un'unica riga.
CORSapp.enableCors({ origin: [...] }) con whitelist esplicita di domini, mai origin: '*' in produzione se l'API richiede credenziali.
Rate Limiting@nestjs/throttler per limitare le richieste per IP, essenziale su endpoint come /auth/login per mitigare brute-force.
ValidationPipewhitelist + forbidNonWhitelisted per bloccare mass assignment su campi non previsti.
Sanitizzazione Inputclass-validator per la struttura, più sanitizzazione esplicita dell'HTML (es. sanitize-html) se il campo permette markup libero, per prevenire XSS.
// Esempio: rate limiting più aggressivo specifico sul login
import { Throttle } from '@nestjs/throttler';

@Throttle({ default: { limit: 5, ttl: 60000 } }) // max 5 tentativi al minuto per IP
@Post('login')
login(@Body() dto: LoginDto) {
  return this.authService.login(dto);
}

Errori Comuni

1. Dimenticare whitelist/forbidNonWhitelisted nel ValidationPipe

Problema: un client può inviare campi extra non previsti dal DTO (es. role: 'admin').
Causa: ValidationPipe configurato senza whitelist: true.
Soluzione: abilita sempre whitelist e forbidNonWhitelisted globalmente in main.ts.

2. synchronize: true in produzione

Problema: TypeORM può alterare/cancellare colonne o tabelle a runtime.
Causa: confusione tra ambiente di sviluppo e produzione nella configurazione database.
Soluzione: synchronize: false in produzione, gestisci lo schema con migration esplicite (typeorm migration:generate).

3. Circular dependency tra moduli

Problema: errore "Nest cannot resolve dependencies" o crash silenzioso all'avvio.
Causa: due moduli si importano a vicenda direttamente.
Soluzione: usa forwardRef(() => ModuleB) in entrambi i moduli, oppure — meglio — estrai la dipendenza condivisa in un terzo modulo.

4. Esporre le entity direttamente come risposta API

Problema: campi sensibili (es. passwordHash) finiscono nella risposta JSON.
Causa: il controller restituisce l'entity TypeORM così com'è, senza un layer DTO/serializer.
Soluzione: usa @Exclude() di class-transformer sull'entity insieme a ClassSerializerInterceptor globale, o meglio ancora un DTO di risposta esplicito.

5. Password salvate senza hashing

Problema: compromissione del DB espone tutte le password in chiaro.
Causa: omissione (spesso durante il rapid prototyping) del passaggio di hashing.
Soluzione: hash con bcrypt/argon2 sempre, anche in sviluppo — non c'è motivo valido per saltarlo mai.

6. Secret JWT deboli o hardcoded nel codice

Problema: un secret prevedibile permette la falsificazione di token validi.
Causa: secret di default lasciato in produzione, o committato nel repository.
Soluzione: secret generati casualmente (almeno 256 bit), gestiti solo tramite variabili d'ambiente/secret manager, mai nel codice sorgente.

7. Mancata gestione degli errori asincroni nei service

Problema: promise rejection non gestite causano crash del processo Node.
Causa: chiamate async senza try/catch dove serve, o dimenticanza dell'exception filter globale.
Soluzione: lascia che le eccezioni salgano fino all'exception filter globale (comportamento di default di Nest), intercetta solo dove serve un comportamento diverso (fallback, retry).

8. N+1 query non individuate

Problema: l'endpoint diventa lentissimo con la crescita dei dati.
Causa: caricamento di relazioni dentro un loop invece che con un singolo join.
Soluzione: usa relations/leftJoinAndSelect, monitora le query generate con logging: true in sviluppo.

9. CORS aperto a tutti in produzione con credentials

Problema: qualsiasi sito web può effettuare richieste autenticate all'API a nome dell'utente.
Causa: origin: '*' combinato con credentials: true.
Soluzione: whitelist esplicita dei domini autorizzati.

10. Nessun rate limiting sugli endpoint di autenticazione

Problema: attacchi brute-force su login/registrazione.
Causa: @nestjs/throttler non applicato o con soglie troppo permissive.
Soluzione: @Throttle più restrittivo specificamente su login/register/reset-password.

11. DTO di Update identico al DTO di Create senza PartialType

Problema: ogni PATCH richiede tutti i campi obbligatori, rompendo gli update parziali.
Causa: copia/incolla del DTO Create senza rendere i campi opzionali.
Soluzione: class UpdateXDto extends PartialType(CreateXDto) {}.

12. Guard applicati solo a livello di rotta, mai di risorsa

Problema: un utente autenticato può modificare risorse di altri utenti.
Causa: ci si affida solo a @UseGuards(AuthGuard('jwt')) senza controllo di ownership nel service.
Soluzione: verifica esplicita resource.ownerId === user.id (o ruolo admin) nel service, come mostrato nell'esempio PostsService.update.

13. Connessione al database senza pool configurato correttamente

Problema: esaurimento delle connessioni sotto carico, errori "too many clients".
Causa: valori di default del pool non adeguati al traffico reale.
Soluzione: configura esplicitamente extra: { max: 20 } nella connection di TypeORM, calibrato sul piano del database.

14. Upload di file senza limiti di dimensione o tipo

Problema: un utente malintenzionato carica file enormi o eseguibili mascherati da immagini.
Causa: FileInterceptor configurato senza limitsfileFilter.
Soluzione: imposta sempre limits.fileSize e valida il mimetype, come nell'esempio uploadCover.

15. Variabili d'ambiente non validate all'avvio

Problema: l'app si avvia "silenziosamente" con configurazione incompleta e fallisce in modo criptico più tardi.
Causa: nessuno schema di validazione sulle env var.
Soluzione: usa ConfigModule.forRoot({ validationSchema: Joi.object({...}) }) per far fallire il bootstrap immediatamente se manca una variabile critica.

16. Logging di dati sensibili

Problema: password, token o dati personali finiscono nei log in chiaro.
Causa: logging indiscriminato dell'intero body della richiesta.
Soluzione: redigi (mascherare) esplicitamente i campi sensibili prima del logging, o usa i redact path di pino.

17. Test scritti solo per il "happy path"

Problema: bug che emergono solo su input non validi o edge case restano invisibili fino alla produzione.
Causa: pressione sui tempi, copertura di test solo sul flusso principale.
Soluzione: testa esplicitamente errori 4xx attesi (validazione, permessi, not found), non solo i 2xx.

18. Versionamento dell'API assente

Problema: ogni modifica breaking rompe i client esistenti senza preavviso.
Causa: nessun prefisso di versione (/api/v1) o strategia di versioning Nest configurata.
Soluzione: app.setGlobalPrefix('api/v1') fin dal primo giorno, anche su progetti piccoli.

19. Dipendenze non aggiornate con vulnerabilità note

Problema: l'API resta esposta a CVE pubblicamente documentate.
Causa: mancanza di un processo di audit periodico delle dipendenze.
Soluzione: npm audit integrato in CI, aggiornamenti regolari con Dependabot/Renovate.

20. Nessuna distinzione tra errori di validazione e errori di dominio

Problema: tutti gli errori tornano come generico 500, rendendo impossibile per il client distinguere un input errato da un problema del server.
Causa: uso di Error generico invece delle classi HttpException specifiche di Nest.
Soluzione: usa sempre le eccezioni tipizzate corrette (BadRequestException, NotFoundException, ForbiddenException, ConflictException...) — è il modo in cui Nest comunica lo status code HTTP corretto.

FAQ

1. NestJS è adatto anche a progetti piccoli?

Sì, ma il vero valore emerge quando il progetto cresce: su un piccolo script, la struttura può sembrare eccessiva rispetto a Express puro.

2. Devo usare per forza TypeScript?

Tecnicamente NestJS supporta anche JavaScript puro, ma si perde gran parte del valore (decoratori tipizzati, DI type-safe). È fortemente sconsigliato in produzione.

3. TypeORM o Prisma?

TypeORM si integra più nativamente con i decoratori NestJS ed è più maturo nell'ecosistema Nest; Prisma offre un client generato più type-safe e una migliore developer experience nelle migration, ma richiede un layer di integrazione manuale con Nest.

4. Come gestisco le migration del database?

Con TypeORM: typeorm migration:generate per generarle dal diff delle entity, typeorm migration:run per applicarle in CI/CD, mai synchronize: true in produzione.

5. Come strutturo un progetto molto grande?

Modularizza per dominio (feature module), non per tipo tecnico — evita cartelle globali "controllers/", "services/" che mescolano domini diversi.

6. Come faccio testing dei controller?

Con Test.createTestingModule di @nestjs/testing, mockando i service iniettati; per i service, mocka i repository TypeORM con getRepositoryToken.

7. Qual è la differenza tra Guard e Middleware per l'autenticazione?

Il middleware non ha accesso al contesto di esecuzione di Nest (es. metadati dei decoratori), quindi non può leggere @Roles(). I guard sì — per questo l'autenticazione/autorizzazione va sempre nei guard, non nei middleware.

8. Posso usare GraphQL invece di REST?

Sì, NestJS ha supporto ufficiale sia per Apollo Server che per Mercurius, con lo stesso sistema di moduli/DI — utile se hai bisogno di query flessibili lato client.

9. Come gestisco le transazioni database?

Con DataSource.transaction() di TypeORM, o con il decoratore @Transactional() della libreria typeorm-transactional per una sintassi più dichiarativa.

10. Come implemento il refresh token in modo sicuro?

Memorizza un hash del refresh token lato DB (whitelist), ruotalo ad ogni utilizzo (token rotation), e invalidalo esplicitamente al logout.

11. NestJS supporta i microservizi?

Sì, nativamente, con transport layer per TCP, Redis, RabbitMQ, Kafka, gRPC e NATS tramite @nestjs/microservices.

12. Come faccio il deploy in produzione?

Build con nest build (compila in dist/), poi node dist/main.js; su piattaforme come Railway/Render/Fly.io basta un Dockerfile multi-stage o il buildpack Node nativo.

13. Come gestisco variabili d'ambiente diverse per dev/staging/prod?

ConfigModule.forRoot({ envFilePath: ['.env.${NODE_ENV}', '.env'] }), con le variabili reali iniettate dalla piattaforma di hosting in produzione, non da file .env committati.

14. Come implemento upload verso S3 invece che su disco locale?

Sostituisci lo storage engine di Multer con multer-s3, oppure gestisci l'upload manualmente nel service con l'SDK AWS dopo aver ricevuto il buffer in memoria.

15. Come documento automaticamente l'API?

Con @nestjs/swagger: decoratori @ApiTags, @ApiProperty sui DTO, e SwaggerModule.setup() in main.ts generano una UI OpenAPI interattiva su /api/docs.

16. Come implemento soft delete?

TypeORM supporta nativamente @DeleteDateColumn() sull'entity: softDelete() imposta la colonna invece di eliminare la riga, e le query escludono automaticamente i record eliminati.

17. Come gestisco versioni multiple dell'API contemporaneamente?

Con app.enableVersioning({ type: VersioningType.URI }) e il decoratore @Version('2') sui singoli controller/metodi che devono coesistere con la v1.

18. È necessario un ORM o posso usare query SQL dirette?

Per la maggior parte dei casi un ORM riduce errori e boilerplate; per query molto complesse o performance-critical, TypeORM permette comunque query raw tramite QueryRunner mantenendo il resto dell'app sull'ORM.

19. Come implemento WebSocket in NestJS?

Con @nestjs/websockets e un @WebSocketGateway(), che si integra con Socket.IO o ws mantenendo lo stesso sistema di DI e guard delle rotte REST.

20. Come gestisco i cron job?

Con @nestjs/schedule e il decoratore @Cron('0 0 * * *') su un metodo di un service — utile per pulizia dati, invio digest email, sincronizzazioni periodiche.

21. Come proteggo l'API da attacchi di tipo SQL Injection?

TypeORM/Prisma parametrizzano automaticamente le query — il rischio emerge solo se costruisci query raw concatenando stringhe manualmente, cosa da evitare sempre.

22. Qual è il modo corretto di gestire i secrets in produzione?

Mai nel codice o in file committati: usa le variabili d'ambiente della piattaforma di hosting o un secret manager dedicato (AWS Secrets Manager, Doppler, Railway Variables).

23. Come scalo orizzontalmente un'API NestJS?

L'app deve essere stateless (nessuna sessione in memoria locale — usa JWT o Redis per lo stato condiviso), poi si replica dietro un load balancer; per WebSocket serve un adapter Redis per sincronizzare le connessioni tra istanze.

24. Come gestisco la retrocompatibilità quando cambio uno schema di risposta?

Introduci un nuovo campo invece di rinominare quello esistente, deprecalo gradualmente con documentazione chiara, e usa il versioning dell'API per i cambiamenti realmente breaking.

25. Vale la pena scrivere test E2E oltre agli unit test per un'API NestJS?

Sì — @nestjs/testing con supertest permette di testare l'intera pipeline (guard, pipe, interceptor inclusi) chiamando gli endpoint reali su un'istanza dell'app in memoria, catturando problemi di integrazione che gli unit test da soli non vedono.

Risorse e Documentazione Ufficiale

Per approfondire ogni singolo argomento trattato in questa guida, la documentazione ufficiale resta sempre la fonte più affidabile e aggiornata:

  • NestJS — documentazione ufficiale: docs.nestjs.com, in particolare le sezioni su Guard, Interceptor, Pipe ed Exception Filter.
  • TypeORM — documentazione ufficiale: typeorm.io, per relazioni, migration e query builder avanzato.
  • Passport.js: passportjs.org, la libreria di autenticazione su cui si basa @nestjs/passport.
  • class-validator: repository ufficiale su GitHub, con l'elenco completo dei decoratori di validazione disponibili oltre a quelli usati in questa guida.
  • OWASP API Security Top 10: la checklist di riferimento per la sicurezza delle API REST, complementare alla sezione Sicurezza di questa guida.
  • jwt.io: strumento per ispezionare e decodificare token JWT durante il debug dell'autenticazione.

Articoli Correlati Consigliati

Questa guida copre l'intero ciclo di vita di una REST API NestJS, ma ogni sezione può diventare un approfondimento a sé stante. Ecco i temi naturalmente collegati che vale la pena esplorare in seguito:

  • Modularizzare un progetto NestJS di grandi dimensioni: feature module, shared module e barrel file.
  • Dependency Injection in NestJS spiegata con esempi: provider custom, factory provider, token di iniezione.
  • Autenticazione JWT e Refresh Token in profondità: token rotation, revoca e gestione multi-dispositivo.
  • Role Based Access Control (RBAC) avanzato: permessi granulari oltre ai semplici ruoli.
  • Validazione con ValidationPipe e class-validator: validatori custom e messaggi di errore localizzati.
  • Logging centralizzato in NestJS con Pino e correlazione delle richieste tramite request ID.
  • Exception Filter personalizzati per domini specifici (es. errori di pagamento, errori di dominio).
  • Upload di file avanzato con Multer: storage su S3, validazione del contenuto reale dei file.
  • Scheduler e Cron Job in NestJS con @nestjs/schedule.
  • WebSocket con NestJS: notifiche realtime e scaling con Redis adapter.
  • Testing di controller e service NestJS: unit test, mock dei repository, test E2E con Supertest.

Conclusione

Costruire una REST API con NestJS significa investire in una struttura che scala con la complessità del progetto: moduli ben isolati, Dependency Injection per la testabilità, guard/interceptor/pipe per separare i cross-cutting concern dalla logica di business, e un ecosistema — TypeORM, Passport, class-validator, Swagger — che copre in modo maturo ogni esigenza reale di produzione. L'esempio Blog API costruito in questa guida (CRUD, JWT con refresh token, RBAC, upload file, logging centralizzato, gestione degli errori) è lo scheletro riutilizzabile per la maggior parte delle API NestJS che incontrerai nella pratica.

Prossimi passi consigliati: approfondisci la modularizzazione di progetti NestJS di grandi dimensioni, l'autenticazione JWT e refresh token nel dettaglio, il pattern RBAC con permessi granulari, e le tecniche di testing per controller e service — argomenti che completano naturalmente questa guida.

💬 Note dei lettori

0 note

Scrivi una nota

Condividi la tua opinione, un suggerimento o un complimento

Ultime note

Nessuna nota ancora. Sii il primo a commentare!