NestJS
Framework Node.js progressivo e TypeScript-first per la costruzione di applicazioni server-side scalabili.
Installazione
Sezione intitolata “Installazione”Creare un Nuovo Progetto
Sezione intitolata “Creare un Nuovo Progetto”| Comando | Descrizione |
|---|---|
npm i -g @nestjs/cli | Installa la CLI NestJS globalmente |
nest new project-name | Crea un nuovo progetto NestJS |
nest new project-name --strict | Crea progetto con TypeScript strict |
nest new project-name -p pnpm | Crea progetto usando pnpm |
nest new project-name -p yarn | Crea progetto usando Yarn |
Comandi di Sviluppo
Sezione intitolata “Comandi di Sviluppo”| Comando | Descrizione |
|---|---|
npm run start:dev | Avvia il server di sviluppo con hot reload |
npm run start:debug | Avvia con modalita debug e hot reload |
npm run start:prod | Avvia il server di produzione |
npm run build | Build del progetto |
npm run test | Esegui test unitari |
npm run test:watch | Esegui test in modalita watch |
npm run test:cov | Esegui test con report di copertura |
npm run test:e2e | Esegui test end-to-end |
npm run lint | Lint del codice sorgente |
Struttura del Progetto
Sezione intitolata “Struttura del Progetto”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
Generazione Codice CLI
Sezione intitolata “Generazione Codice CLI”Comandi di Generazione
Sezione intitolata “Comandi di Generazione”| Comando | Descrizione |
|---|---|
nest generate module users | Genera un nuovo modulo |
nest generate controller users | Genera un nuovo controller |
nest generate service users | Genera un nuovo servizio |
nest generate resource users | Genera risorsa CRUD completa (modulo + controller + servizio + DTO) |
nest generate guard auth | Genera un guard di autenticazione |
nest generate pipe validation | Genera una pipe di validazione |
nest generate interceptor logging | Genera un interceptor |
nest generate middleware logger | Genera un middleware |
nest generate filter http-exception | Genera un filtro per le eccezioni |
nest generate decorator roles | Genera un decoratore personalizzato |
nest generate class user.entity | Genera una classe semplice |
nest generate interface user | Genera un’interfaccia |
nest g res users --no-spec | Genera risorsa senza file di test |
nest g mo users --flat | Genera senza creare sottocartella |
nest info | Visualizza informazioni e dipendenze del progetto |
Generazione di Risorse
Sezione intitolata “Generazione di Risorse”# 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)
Decoratori dei Moduli
Sezione intitolata “Decoratori dei Moduli”| Comando | Descrizione |
|---|---|
@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) |
Moduli Dinamici
Sezione intitolata “Moduli Dinamici”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 {}
Controller
Sezione intitolata “Controller”Decoratori di Route
Sezione intitolata “Decoratori di Route”| Comando | Descrizione |
|---|---|
@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 |
Decoratori dei Parametri
Sezione intitolata “Decoratori dei Parametri”| Comando | Descrizione |
|---|---|
@Param('id') id: string | Estrai parametro di route |
@Param('id', ParseIntPipe) id: number | Estrai e converti in intero |
@Query('page') page: string | Estrai parametro di query |
@Query() query: PaginationDto | Estrai tutti i parametri di query come DTO |
@Body() dto: CreateUserDto | Estrai e tipizza il corpo della richiesta |
@Body('email') email: string | Estrai campo specifico dal corpo |
@Headers('authorization') auth: string | Estrai header della richiesta |
@Ip() ip: string | Estrai indirizzo IP del client |
@Req() request: Request | Accedi all’oggetto request grezzo |
@Res() response: Response | Accedi 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 |
Esempio di Controller
Sezione intitolata “Esempio di Controller”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)
}
}
Servizi e Provider
Sezione intitolata “Servizi e Provider”Dependency Injection
Sezione intitolata “Dependency Injection”| Comando | Descrizione |
|---|---|
@Injectable() | Segna la classe come provider iniettabile |
constructor(private usersService: UsersService) | Inietta servizio tramite costruttore |
@Inject('TOKEN') private config | Inietta 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?: MyService | Iniezione di dipendenza opzionale |
{ provide: 'TOKEN', scope: Scope.REQUEST } | Provider con scope per richiesta |
Esempio di Servizio
Sezione intitolata “Esempio di Servizio”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`)
}
}
}
Validazione e DTO
Sezione intitolata “Validazione e DTO”Configurazione Class Validator
Sezione intitolata “Configurazione Class Validator”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)
}
Esempi di DTO
Sezione intitolata “Esempi di DTO”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']) {}
Guard e Autenticazione
Sezione intitolata “Guard e Autenticazione”Configurazione Guard
Sezione intitolata “Configurazione Guard”| Comando | Descrizione |
|---|---|
@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 |
Reflector | Leggi metadati nei guard |
Autenticazione JWT
Sezione intitolata “Autenticazione JWT”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()
}
}
Controllo di Accesso Basato sui Ruoli
Sezione intitolata “Controllo di Accesso Basato sui Ruoli”// 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)
}
}
Middleware, Interceptor e Pipe
Sezione intitolata “Middleware, Interceptor e Pipe”Middleware
Sezione intitolata “Middleware”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('*')
}
}
Interceptor
Sezione intitolata “Interceptor”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`)
}),
)
}
}
Integrazione Database
Sezione intitolata “Integrazione Database”Configurazione TypeORM
Sezione intitolata “Configurazione TypeORM”| Comando | Descrizione |
|---|---|
npm install @nestjs/typeorm typeorm pg | Installa 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 |
Configurazione Prisma
Sezione intitolata “Configurazione Prisma”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 } })
}
}
Gestione delle Eccezioni
Sezione intitolata “Gestione delle Eccezioni”Eccezioni Integrate
Sezione intitolata “Eccezioni Integrate”| Comando | Descrizione |
|---|---|
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 |
Filtro Eccezioni Personalizzato
Sezione intitolata “Filtro Eccezioni Personalizzato”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())
Configurazione
Sezione intitolata “Configurazione”Configurazione dell’Ambiente
Sezione intitolata “Configurazione dell’Ambiente”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)
}
}
Testing
Sezione intitolata “Testing”Test Unitari
Sezione intitolata “Test Unitari”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()
})
})
Test E2E
Sezione intitolata “Test E2E”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()
})
})
})
Buone Pratiche
Sezione intitolata “Buone Pratiche”-
Usa il generatore di risorse —
nest g resourcecrea l’intero scaffold CRUD (modulo, controller, servizio, DTO, entity, test) in pochi secondi e mantiene il tuo progetto coerente. -
Abilita ValidationPipe globale con whitelist — imposta
whitelist: trueeforbidNonWhitelisted: trueper rimuovere o rifiutare automaticamente i campi non previsti da tutte le richieste. -
Usa i mapped types per i DTO —
PartialType,PickType,OmitTypeeIntersectionTypemantengono i tuoi DTO DRY derivando i DTO di aggiornamento/query dal DTO di creazione. -
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. -
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.
-
Usa ConfigModule per ogni accesso all’ambiente — non leggere mai
process.envdirettamente nei servizi. UsaConfigServicecon schemi di validazione cosi le variabili mancanti falliscono velocemente all’avvio. -
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.
-
Scrivi test unitari e e2e — testa unitariamente i servizi con dipendenze mockate usando
Test.createTestingModule(), e testa e2e gli endpoint HTTP consupertest. -
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.
-
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.
-
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. -
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.