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.


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