Salta ai contenuti

NestJS

Framework Node.js progressivo e TypeScript-first per la costruzione di applicazioni server-side scalabili.

ComandoDescrizione
npm i -g @nestjs/cliInstalla la CLI NestJS globalmente
nest new project-nameCrea un nuovo progetto NestJS
nest new project-name --strictCrea progetto con TypeScript strict
nest new project-name -p pnpmCrea progetto usando pnpm
nest new project-name -p yarnCrea progetto usando Yarn
ComandoDescrizione
npm run start:devAvvia il server di sviluppo con hot reload
npm run start:debugAvvia con modalita debug e hot reload
npm run start:prodAvvia il server di produzione
npm run buildBuild del progetto
npm run testEsegui test unitari
npm run test:watchEsegui test in modalita watch
npm run test:covEsegui test con report di copertura
npm run test:e2eEsegui test end-to-end
npm run lintLint del codice sorgente
src/
├── app.controller.ts       # Controller principale
├── app.controller.spec.ts  # Test unitario del controller
├── app.module.ts           # Modulo principale
├── app.service.ts          # Servizio principale
├── main.ts                 # Punto di ingresso dell'applicazione
├── users/
│   ├── users.module.ts     # Modulo funzionalita
│   ├── users.controller.ts # Controller funzionalita
│   ├── users.service.ts    # Servizio funzionalita
│   ├── dto/
│   │   ├── create-user.dto.ts
│   │   └── update-user.dto.ts
│   ├── entities/
│   │   └── user.entity.ts
│   └── users.controller.spec.ts
├── auth/
│   ├── auth.module.ts
│   ├── auth.guard.ts
│   ├── auth.service.ts
│   └── strategies/
│       └── jwt.strategy.ts
└── common/
    ├── filters/
    │   └── http-exception.filter.ts
    ├── interceptors/
    │   └── logging.interceptor.ts
    └── pipes/
        └── validation.pipe.ts
ComandoDescrizione
nest generate module usersGenera un nuovo modulo
nest generate controller usersGenera un nuovo controller
nest generate service usersGenera un nuovo servizio
nest generate resource usersGenera risorsa CRUD completa (modulo + controller + servizio + DTO)
nest generate guard authGenera un guard di autenticazione
nest generate pipe validationGenera una pipe di validazione
nest generate interceptor loggingGenera un interceptor
nest generate middleware loggerGenera un middleware
nest generate filter http-exceptionGenera un filtro per le eccezioni
nest generate decorator rolesGenera un decoratore personalizzato
nest generate class user.entityGenera una classe semplice
nest generate interface userGenera un’interfaccia
nest g res users --no-specGenera risorsa senza file di test
nest g mo users --flatGenera senza creare sottocartella
nest infoVisualizza informazioni e dipendenze del progetto
# Risorsa CRUD completa (workflow piu comune)
nest g resource users

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

# Crea:
# src/users/users.module.ts
# src/users/users.controller.ts
# src/users/users.service.ts
# src/users/dto/create-user.dto.ts
# src/users/dto/update-user.dto.ts
# src/users/entities/user.entity.ts
# src/users/users.controller.spec.ts
# Aggiorna: src/app.module.ts (importa UsersModule)
ComandoDescrizione
@Module({ imports: [UsersModule] })Importa un altro modulo
@Module({ controllers: [UsersController] })Registra controller
@Module({ providers: [UsersService] })Registra provider/servizi
@Module({ exports: [UsersService] })Esporta provider per altri moduli
@Global()Rendi il modulo disponibile globalmente (usa con parsimonia)
import { Module, DynamicModule } from '@nestjs/common'

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

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

// Utilizzo in AppModule
@Module({
  imports: [
    DatabaseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
ComandoDescrizione
@Controller('users')Definisci controller con prefisso di route
@Get()Gestisci richiesta GET
@Post()Gestisci richiesta POST
@Put(':id')Gestisci richiesta PUT con parametro
@Patch(':id')Gestisci richiesta PATCH
@Delete(':id')Gestisci richiesta DELETE
@All('*')Gestisci tutti i metodi HTTP
ComandoDescrizione
@Param('id') id: stringEstrai parametro di route
@Param('id', ParseIntPipe) id: numberEstrai e converti in intero
@Query('page') page: stringEstrai parametro di query
@Query() query: PaginationDtoEstrai tutti i parametri di query come DTO
@Body() dto: CreateUserDtoEstrai e tipizza il corpo della richiesta
@Body('email') email: stringEstrai campo specifico dal corpo
@Headers('authorization') auth: stringEstrai header della richiesta
@Ip() ip: stringEstrai indirizzo IP del client
@Req() request: RequestAccedi all’oggetto request grezzo
@Res() response: ResponseAccedi all’oggetto response grezzo
@HttpCode(201)Imposta codice di stato HTTP personalizzato
@Header('Cache-Control', 'none')Imposta header di risposta
@Redirect('/new-url', 301)Risposta di redirect
import {
  Controller, Get, Post, Put, Delete, Param, Query, Body,
  HttpCode, HttpStatus, ParseIntPipe, UseGuards, UseInterceptors,
} from '@nestjs/common'
import { UsersService } from './users.service'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'
import { JwtAuthGuard } from '../auth/jwt-auth.guard'
import { LoggingInterceptor } from '../common/interceptors/logging.interceptor'

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

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

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

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

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

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.remove(id)
  }
}
ComandoDescrizione
@Injectable()Segna la classe come provider iniettabile
constructor(private usersService: UsersService)Inietta servizio tramite costruttore
@Inject('TOKEN') private configInietta per token personalizzato
{ provide: 'TOKEN', useValue: value }Registra provider di valore
{ provide: 'TOKEN', useFactory: () => ... }Registra provider factory
{ provide: 'TOKEN', useClass: MyClass }Registra provider di classe
{ provide: 'TOKEN', useExisting: OtherService }Alias di provider esistente
@Optional() private service?: MyServiceIniezione di dipendenza opzionale
{ provide: 'TOKEN', scope: Scope.REQUEST }Provider con scope per richiesta
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { User } from './entities/user.entity'
import { CreateUserDto } from './dto/create-user.dto'
import { UpdateUserDto } from './dto/update-user.dto'

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

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

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

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

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

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,           // rimuovi proprieta sconosciute
    forbidNonWhitelisted: true, // lancia errore per proprieta sconosciute
    transform: true,           // trasforma automaticamente i payload in tipi DTO
    transformOptions: {
      enableImplicitConversion: true, // converti stringhe di query in numeri/booleani
    },
  }))
  await app.listen(3000)
}
import {
  IsString, IsEmail, IsOptional, IsInt, Min, Max,
  MinLength, MaxLength, IsEnum, ValidateNested, IsArray,
} from 'class-validator'
import { Type } from 'class-transformer'
import { PartialType, OmitType, PickType, IntersectionType } from '@nestjs/mapped-types'

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

  @IsEmail()
  email: string

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

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

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

// Genera automaticamente DTO di aggiornamento (tutti i campi opzionali)
export class UpdateUserDto extends PartialType(CreateUserDto) {}

// Seleziona campi specifici
export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}

// Ometti campi specifici
export class PublicUserDto extends OmitType(CreateUserDto, ['password']) {}
ComandoDescrizione
@UseGuards(AuthGuard)Applica guard al controller o alla route
@UseGuards(AuthGuard, RolesGuard)Concatena guard multipli
app.useGlobalGuards(new AuthGuard())Registra guard globale
canActivate(context: ExecutionContext)Implementa la logica del guard
@SetMetadata('roles', ['admin'])Imposta metadati personalizzati per i guard
ReflectorLeggi metadati nei guard
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
// auth/auth.module.ts
@Module({
  imports: [
    UsersModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        secret: config.get('JWT_SECRET'),
        signOptions: { expiresIn: '1h' },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

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

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

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

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

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

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

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

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

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

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

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

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

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

// Trasforma la forma della risposta
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { data: T }> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<{ data: T }> {
    return next.handle().pipe(
      map(data => ({ data, timestamp: new Date().toISOString() })),
    )
  }
}

// Misura il tempo di esecuzione
@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now()
    return next.handle().pipe(
      tap(() => {
        const duration = Date.now() - start
        const response = context.switchToHttp().getResponse()
        response.setHeader('X-Response-Time', `${duration}ms`)
      }),
    )
  }
}
ComandoDescrizione
npm install @nestjs/typeorm typeorm pgInstalla TypeORM con PostgreSQL
TypeOrmModule.forRoot({ type: 'postgres', ... })Configura connessione nell’AppModule
TypeOrmModule.forFeature([User])Registra entity nel modulo funzionalita
@InjectRepository(User) private repo: Repository<User>Inietta repository
npm install @prisma/client
npm install -D prisma
npx prisma init
// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'
import { PrismaClient } from '@prisma/client'

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

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

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

// Utilizzo in un servizio
@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

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

  findOne(id: number) {
    return this.prisma.user.findUniqueOrThrow({ where: { id } })
  }
}
ComandoDescrizione
throw new BadRequestException('Invalid input')400 Bad Request
throw new UnauthorizedException('Login required')401 Unauthorized
throw new ForbiddenException('Access denied')403 Forbidden
throw new NotFoundException('User not found')404 Not Found
throw new ConflictException('Email taken')409 Conflict
throw new UnprocessableEntityException('Validation failed')422 Unprocessable
throw new InternalServerErrorException()500 Internal Server Error
import {
  ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
} from '@nestjs/common'
import { Request, Response } from 'express'

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

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

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

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

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

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

// Utilizzo in qualsiasi servizio
@Injectable()
export class AppService {
  constructor(private config: ConfigService) {}

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

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

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

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

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

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

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

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

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

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

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

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

  it('/users (GET) returns paginated list', () => {
    return request(app.getHttpServer())
      .get('/users?page=1&limit=10')
      .expect(200)
      .expect((res) => {
        expect(res.body.items).toBeInstanceOf(Array)
        expect(res.body.total).toBeDefined()
      })
  })
})
  1. Usa il generatore di risorsenest g resource crea l’intero scaffold CRUD (modulo, controller, servizio, DTO, entity, test) in pochi secondi e mantiene il tuo progetto coerente.

  2. Abilita ValidationPipe globale con whitelist — imposta whitelist: true e forbidNonWhitelisted: true per rimuovere o rifiutare automaticamente i campi non previsti da tutte le richieste.

  3. Usa i mapped types per i DTOPartialType, PickType, OmitType e IntersectionType mantengono i tuoi DTO DRY derivando i DTO di aggiornamento/query dal DTO di creazione.

  4. Preferisci l’iniezione tramite costruttore — lascia che NestJS gestisca la dependency injection attraverso i costruttori piuttosto che usare token @Inject() manuali, a meno che non servano provider personalizzati.

  5. Mantieni i controller snelli — i controller dovrebbero solo analizzare l’input e restituire l’output. Sposta tutta la logica di business nei servizi per testabilita e riuso.

  6. Usa ConfigModule per ogni accesso all’ambiente — non leggere mai process.env direttamente nei servizi. Usa ConfigService con schemi di validazione cosi le variabili mancanti falliscono velocemente all’avvio.

  7. Delimita guard e interceptor appropriatamente — applicali a livello di controller o route con decoratori piuttosto che globalmente, a meno che non si applichino davvero ovunque.

  8. Scrivi test unitari e e2e — testa unitariamente i servizi con dipendenze mockate usando Test.createTestingModule(), e testa e2e gli endpoint HTTP con supertest.

  9. Usa Prisma o TypeORM in modo coerente — scegli un ORM e usalo in tutto il progetto. Prisma offre migliore sicurezza dei tipi; TypeORM offre piu flessibilita con i decoratori.

  10. Struttura per funzionalita, non per tipo — raggruppa i file per dominio (users/, auth/, orders/) piuttosto che per tipo (controllers/, services/). Questo mantiene il codice correlato insieme e i moduli autonomi.

  11. Gestisci gli errori con le eccezioni — lancia eccezioni HTTP integrate (NotFoundException, ConflictException) dai servizi. Usa i filtri per le eccezioni per la formattazione personalizzata degli errori.

  12. Usa async/await in modo coerente — NestJS gestisce le promise nativamente. Restituisci valori asincroni dai metodi dei servizi e lascia che il framework serializzi la risposta.