NestJS impone una struttura a moduli, controller e provider sopra Node.js, ricalca l’architettura del frontend Angular e porta la dependency injection lato server in un ecosistema che finora ne aveva fatto a meno. Kamil Myśliwiec ha avviato il progetto a febbraio 2017; la prima release taggata pubblicamente è la v4.4.0 del 23 novembre 2017, e la linea 4.x è quella che si usa in questi mesi.
Contesto
Dal 2010 Express è il riferimento per le applicazioni HTTP su Node: un router e una catena di middleware, niente di più. La sua minimalità è una scelta voluta, e per molti servizi basta. Il problema salta fuori quando il codice cresce: senza convenzioni imposte, ogni team si inventa la propria struttura di cartelle, il proprio modo di istanziare le dipendenze, la propria linea fra logica di trasporto e logica di dominio. Su una codebase TypeScript da decine di migliaia di righe quella libertà diventa costo di manutenzione.
Chi viene da Angular sul frontend, o da Spring nel mondo Java, conosce l’alternativa: un framework opinato che decide al posto tuo dove vanno le cose. NestJS nasce per colmare questo vuoto su Node, e per farlo prende in prestito il vocabolario di Angular — moduli, provider iniettabili, decoratori — e lo porta sul server.
Architettura
L’unità di base è il modulo, dichiarato con @Module({ imports, controllers, providers }). Dentro, i controller gestiscono le rotte HTTP e i provider (di solito servizi) contengono la logica. I due lati si collegano tramite dependency injection: un controller dichiara nel costruttore i servizi che gli servono, e il container di Nest li risolve e li inietta.
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
}
Le rotte si dichiarano con decoratori — @Get, @Post, @Param, @Body — invece di registrarle a mano su un router. Il modello è quasi identico ai componenti e ai servizi di Angular, e la somiglianza non è casuale: l’obiettivo dichiarato è far ritrovare gli stessi schemi a chi lavora full-stack in TypeScript su entrambi i lati, senza dover cambiare modo di ragionare.
Attorno al ciclo richiesta-risposta la linea 4.x espone alcuni punti di estensione ordinati:
- Pipe per validare e trasformare l’input prima che arrivi all’handler (la validazione di solito si appoggia a
class-validator); - Guard per decidere se una richiesta può proseguire, dove di solito vive il controllo di autenticazione e autorizzazione;
- Interceptor per avvolgere l’esecuzione dell’handler, comodi per logging, trasformazione della risposta, caching;
- Exception filter per accentrare la gestione degli errori e la forma delle risposte di errore.
Sotto, in questa versione NestJS resta costruito su Express: l’oggetto request è quello di Express, e i middleware Express continuano a funzionare. Il framework aggiunge struttura, non rimpiazza il runtime HTTP.
Il punto critico
Tutta l’impalcatura poggia su una funzionalità che TypeScript considera ancora sperimentale: i decoratori. Per usarli vanno attivati experimentalDecorators e emitDecoratorMetadata nel tsconfig.json. Il primo abilita la sintassi @; il secondo fa sì che il compilatore emetta, a runtime, i metadati di tipo dei parametri.
È questo secondo flag a rendere possibile la dependency injection senza configurazione esplicita. Quando scrivi constructor(private usersService: UsersService), il compilatore — grazie a emitDecoratorMetadata — registra il tipo UsersService come metadato del parametro, recuperabile a runtime tramite la libreria reflect-metadata. Nest legge quei metadati e capisce cosa istanziare. Senza l’informazione di tipo emessa dal compilatore, il container non avrebbe niente da iniettare.
La dipendenza ha conseguenze concrete. I decoratori non sono uno standard JavaScript stabilizzato: la proposta TC39 è in evoluzione da anni, e il comportamento dei decoratori TypeScript è legato all’implementazione attuale del compilatore, non a una specifica finale. Chi adotta NestJS oggi accetta che un pezzo portante del proprio backend dipenda da una feature experimental di TypeScript e da reflect-metadata, una libreria che implementa una proposta di reflection anch’essa non finalizzata. Funziona, ed è in produzione in parecchi progetti, ma conviene tenerlo presente: a livello di linguaggio il terreno non è ancora fermo.
Implicazioni
L’astrazione ha un costo di apprendimento che va oltre Express. Per essere produttivi servono il modello a moduli, la semantica del container DI, la differenza fra le quattro categorie di estensione (pipe, guard, interceptor, filter) e quando usare ciascuna. Un endpoint che con Express scrivi in cinque righe, in Nest ti chiede di mettere la logica nel posto giusto rispetto a queste convenzioni. Il guadagno si vede sui numeri grandi: su servizi estesi e team numerosi, una struttura imposta taglia le decisioni arbitrarie e rende prevedibile il codice di un collega.
C’è poi un’affinità organizzativa. Un team che usa Angular sul frontend ritrova gli stessi pattern in Nest, e quella continuità vale davvero quando le stesse persone lavorano su entrambi i lati in TypeScript. Per un team che non usa Angular e non prevede di crescere oltre un servizio compatto, gran parte di questa struttura resta sovradimensionata rispetto al problema.
Limiti
La linea 4.x su cui ragiono è ancora giovane: poco più di un anno di sviluppo, un ecosistema di moduli ufficiali in crescita ma non maturo come quello di Spring, e una superficie di API che continua a muoversi fra le minor. Conviene guardare le note di rilascio prima di fissare una versione in produzione.
Il legame con i decoratori sperimentali, infine, è una scommessa su come evolveranno TypeScript e la proposta TC39. Finché restano compatibili con l’uso che NestJS ne fa, il framework regge; un cambio di rotta a livello di linguaggio richiederebbe un adattamento tutt’altro che banale. Per ora la dipendenza ha tenuto, ma è la riga che terrei sott’occhio valutando il framework per un sistema con orizzonte lungo.
- https://github.com/nestjs/nest
- https://github.com/nestjs/nest/releases
- https://www.typescriptlang.org/docs/handbook/decorators.html
- https://www.npmjs.com/package/reflect-metadata
- https://expressjs.com/
- https://www.noze.it/insights/nestjs-1-0/
Immagine di copertina: Logo ufficiale di NestJS: un marchio stilizzato a forma di scudo/testa di felino in rosso magenta su sfondo trasparente — logo di NestJS, pubblico dominio — https://commons.wikimedia.org/wiki/File:NestJS.svg