Cos'è NestJS e quando conviene usarlo
Come funziona NestJS: moduli, iniezione delle dipendenze e struttura imposta, perché ricorda Angular e quando invece Express resta la scelta giusta.
Un backend Express che cresce arriva sempre allo stesso punto: file da mille righe, cartelle organizzate come sembrava sensato quel giorno, e nessuno che ricordi perché la logica degli ordini stia dentro il file degli utenti. NestJS nasce da quella frustrazione e propone una risposta netta: la struttura non la decidi tu. In questo articolo trovi come funziona, perché somiglia ad Angular e quando invece è una complicazione che non ti serve.
Cos'è
NestJS è un framework Node per costruire backend con una struttura già decisa: moduli, controller e servizi, con l'iniezione delle dipendenze come meccanismo centrale.
Sotto usa Express (o Fastify, se lo configuri): non sostituisce il livello HTTP, lo incapsula. Quello che aggiunge è un'architettura.
TypeScript non è opzionale, o meglio: puoi tecnicamente evitarlo, ma l'intero progetto è costruito sui decoratori e sui tipi, e usarlo senza significa rinunciare a metà del suo valore. Se non hai dimestichezza, parti da cos'è TypeScript.
Perché somiglia ad Angular
È la prima cosa che si nota, e non è una coincidenza: NestJS ha preso in prestito da Angular l'organizzazione in moduli, i decoratori e l'iniezione delle dipendenze.
La conseguenza pratica è doppia. Chi viene da Angular si trova a casa e diventa produttivo in un pomeriggio. Chi viene da Express si chiede perché servano tre file e un decoratore per rispondere a una richiesta HTTP.
Entrambe le reazioni sono legittime, e indicano la stessa cosa: NestJS scambia semplicità iniziale con struttura a lungo termine. Se il progetto è piccolo, paghi il primo prezzo senza incassare il secondo.
I tre pezzi
Tutto ruota attorno a tre concetti che si ripetono ovunque.
Il controller riceve la richiesta HTTP e non fa altro: legge i parametri e delega.
import { Controller, Get, Post, Body, Param } from "@nestjs/common";
import { OrdiniService } from "./ordini.service";
@Controller("ordini")
export class OrdiniController {
constructor(private readonly ordiniService: OrdiniService) {}
@Get(":id")
trova(@Param("id") id: string) {
return this.ordiniService.trova(id);
}
@Post()
crea(@Body() dati: CreaOrdineDto) {
return this.ordiniService.crea(dati);
}
}
Il servizio contiene la logica e non sa nulla di HTTP. È la separazione che rende il codice testabile: puoi provare la logica degli ordini senza simulare una richiesta.
import { Injectable, NotFoundException } from "@nestjs/common";
@Injectable()
export class OrdiniService {
constructor(private readonly repo: OrdiniRepository) {}
async trova(id: string) {
const ordine = await this.repo.findById(id);
if (!ordine) throw new NotFoundException(`Ordine ${id} non trovato`);
return ordine;
}
}
Il modulo tiene insieme i pezzi e dichiara cosa esporta verso il resto dell'applicazione:
import { Module } from "@nestjs/common";
@Module({
controllers: [OrdiniController],
providers: [OrdiniService, OrdiniRepository],
exports: [OrdiniService],
})
export class OrdiniModule {}
Il vantaggio non è estetico. In un progetto Nest, la logica degli ordini sta in ordini/, quella dei pagamenti in pagamenti/, e chi entra nel team il primo giorno sa già dove guardare. È lo stesso ragionamento per cui Angular piace alle aziende: la struttura sopravvive al turnover.
L'iniezione delle dipendenze, spiegata concretamente
È il meccanismo che spaventa di più e in realtà risolve un problema banale.
Senza, il servizio crea da sé ciò che gli serve:
class OrdiniService {
private repo = new OrdiniRepository(new Database(CONFIG));
}
Tre problemi: il servizio deve sapere come si costruisce il repository, ogni cambiamento del costruttore si propaga ovunque, e nei test non puoi sostituirlo con uno finto senza toccare il codice.
Con l'iniezione, il servizio dichiara cosa gli serve e il framework glielo passa. Nei test sostituisci l'oggetto vero con uno simulato in una riga:
const modulo = await Test.createTestingModule({
providers: [
OrdiniService,
{ provide: OrdiniRepository, useValue: repositoryFinto },
],
}).compile();
È qui che la struttura ripaga davvero: scrivere test su un'applicazione Nest è sensibilmente più semplice che su un Express organizzato liberamente.
Cosa trovi già pronto
La differenza più concreta rispetto a Express è che le cose che lì assembli con librerie di terze parti, qui ci sono già e parlano la stessa lingua:
Validazione dichiarativa tramite le classi DTO: dichiari la forma dei dati in entrata e le richieste malformate vengono respinte prima di arrivare al controller.
Guardie per autenticazione e autorizzazione, applicabili a una rotta o a un intero modulo.
Interceptor per le cose trasversali: log, trasformazione delle risposte, cache.
Gestione centralizzata degli errori, con le eccezioni HTTP già definite.
Documentazione API generata dai decoratori, invece che scritta a mano e dimenticata. Il concetto di API REST qui è di casa.
Supporto integrato per WebSocket, code, attività pianificate, microservizi e GraphQL.
Su Express ognuna di queste è una scelta, una libreria e una configurazione. Su Nest è una convenzione.
Errori comuni
Usarlo su un progetto piccolo. Per un'API con sei endpoint, la struttura è sovrastruttura: scrivi più file di quanto scriveresti codice.
Mettere la logica nel controller. Vanifica l'intera architettura. Il controller riceve e delega, punto.
Dimenticare di dichiarare un provider nel modulo. L'errore di iniezione che ne risulta è criptico e fa perdere tempo la prima volta: se un servizio "non esiste", quasi sempre manca dai providers.
Trattare i moduli come cartelle. Un modulo dichiara un confine, non un raggruppamento estetico. Se tutto importa tutto, hai la complessità senza il beneficio.
Sceglierlo perché "è più professionale". Non lo è: è più strutturato, il che aiuta su progetti grandi e pesa su quelli piccoli.
Nest, Express o Fastify
| Express | NestJS | Fastify | |
|---|---|---|---|
| Struttura | Libera | Imposta | Libera |
| Curva iniziale | Bassa | Ripida | Bassa |
| TypeScript | Aggiunto | Nativo | Buono |
| Cose già pronte | Poche | Molte | Poche |
| Prestazioni grezze | Buone | Buone | Migliori |
| Adatto a | API piccole | Team e progetti lunghi | Carichi elevati |
Il criterio pratico: quante persone toccheranno questo codice e per quanto tempo. Una persona sola su un progetto di pochi mesi sta meglio con Express. Un team su un gestionale che vivrà anni sta meglio con Nest.
Quando NON ti serve
La sezione onesta, perché riguarda parecchi casi.
Per un'API piccola o un prototipo, Express fa il lavoro con un quinto dei file. La velocità di partenza conta più dell'ordine quando devi validare un'idea — vedi cos'è un MVP.
Se il backend è già dentro il framework frontend. Con Next.js hai già le rotte API: aggiungere un backend separato raddoppia le cose da mantenere senza un motivo chiaro.
Se lavori da solo e Express non ti ha mai dato problemi. La struttura di Nest risolve un problema di coordinamento tra persone: se non ce l'hai, stai comprando una soluzione senza il problema.
Se ti servono funzioni brevi, il modello è un altro: vedi cosa sono le serverless functions.
Ha senso quando il progetto è destinato a crescere, il team è più di uno, e la disciplina architetturale vale più della velocità iniziale.
In sintesi
NestJS porta su Node l'architettura di Angular: moduli, controller, servizi e iniezione delle dipendenze, con TypeScript come presupposto.
Il valore non è nelle prestazioni ma nella prevedibilità. In un progetto Nest sai dove sta ogni cosa, e questo conta quando il codice sopravvive a chi l'ha scritto.
Il costo è reale e va messo in conto: più file, più concetti, una curva ripida per chi arriva da Express.
Il criterio per decidere è il tempo e le persone, non la dimensione dell'idea. Progetto lungo con più sviluppatori: la struttura ripaga. Progetto personale o prototipo: Express resta la scelta giusta, e non c'è niente di poco professionale in questo.