La documentazione ufficiale spiega cos’è in una sola frase: “Node.js è un runtime JavaScript costruito sul motore JavaScript V8 di Chrome”. Ma cosa vuol dire?

Node.js è un ambiente di esecuzione che permette di eseguire del codice JavaScript come fosse un linguaggio di programmazione, questo grazie all’uso del motore V8 di Chrome che ha 2 caratteristiche importanti: è molto performante, cioè esegue codice JavaScript velocemente usando poche risorse, ed è open-source, questo vuol dire che chiunque può modificarlo e utilizzarlo all’interno di altri progetti. 

Un primo esperimento per introdurre l’uso di JavaScript lato server fu fatto dalla Netscape che, insieme alla vendita dei web server, includeva un ambiente chiamato Netscape LiveWire in grado di creare pagine web dinamiche con l’uso di JS lato server, ma non ebbe molto successo, fino al rilascio di Node.js. Questo è dato dall’introduzione del “Web 2.0” che grazie alle applicazioni come “Gmail”, mostrava al mondo come poteva essere una moderna esperienza sul web.

Ma perché un’applicazione Node.js è più performante rispetto ad altre sviluppate con un diverso linguaggio di programmazione?

Supponiamo di avere un server su cui sia in esecuzione un social network scritto in PHP, a cui sono connessi migliaia di utenti contemporaneamente, e ogni utente effettua delle richieste al server, come pubblicare un commento o mettere “Mi piace” ad un post. Ogni richiesta che arriva al server viene inserita in una coda, poi, per ogni richiesta, viene creato un “operatore” (thread) che si occupa di gestire solo la richiesta che ha preso in carico, controlla cosa ha chiesto l’utente, nel nostro caso dovrà aggiungere un “Mi piace” ad un post di un nostro amico, contatta il database per effettuare l’aggiunta del “Mi piace”, attende che il database gli risponda che ha terminato l’operazione di aggiornamento, invia una risposta all’utente dicendogli che l’operazione è stata completata con successo e, infine, viene eliminato.

A questo punto, possiamo notare che la gestione delle richieste ha un problema, ogni volta il server deve creare un thread per ogni richiesta ricevuta ed eliminarlo una volta terminata l’operazione e, quando effettua operazioni di I/O, dovrà contattare un’altra entità per elaborare la richiesta (database, filesystem) e rimanere in attesa della risposta di quest’ultima, causando uno spreco di risorse. Questo viene chiamato “modello I/O bloccante” e viene usato nella maggior parte dei linguaggi di programmazione. Node.js, invece, usa un “modello I/O non bloccante”, ma per capire come funziona torniamo al nostro esempio.

Sostituiamo il social network scritto in PHP con quello scritto in Node.js e avviamolo. Notiamo subito l’arrivo di migliaia di richieste e Node.js le inserisce in una coda in base all’ordine di arrivo. A questo punto, le richieste dei nostri utenti non verranno più gestite da diversi operatori, ma ce ne sarà uno solo creato all’avvio e finché il server è attivo. L’operatore (event loop) controlla cos’ha chiesto l’utente, nel nostro caso chiede la modifica del nome utente del profilo, quindi è un’operazione bloccante e non la gestisce personalmente, ma contatta il database, gli dice di effettuare l’operazione e di richiamarlo quando ha terminato (crea una callback), così l’operatore può proseguire a gestire le altre richieste. Quando il database termina l’operazione, richiama l’operatore per dirgli che ha effettuato la modifica con successo (genera un evento) e invia una risposta all’utente per dirgli che la modifica è stata effettuata.

In questo modo abbiamo risparmiato le risorse del nostro server e abbiamo un solo thread, chiamato “Event Loop”, che si occupa di gestire le richieste e le risposte, e se vede che c’è un’operazione bloccante, la passa all’entità interessata creando una callback, così, quando termina l’operazione, genera un evento per avvertire l’event loop che ha terminato e può inviare la risposta. Questo è un enorme vantaggio perché, se prima, per gestire migliaia di richieste avevamo bisogno di un server multiprocessore e un grande quantitativo di RAM, ora riusciamo a gestirle anche con un solo processore e poca RAM.

Una volta capito come funziona Node.js, passiamo ad NPM. Che cos’è?

NPM è l’acronimo di “Node Package Manager” (Gestore dei pacchetti di Node) ed è il registro di librerie open-source più grande al mondo. Supponiamo che tu voglia integrare il metodo di pagamento tramite PayPal all’interno del tuo social network per permettere agli utenti di vendere e comprare oggetti senza uscire dalla tua piattaforma. A questo punto hai 3 scelte: la prima è quella di scrivere da zero l’integrazione consultando la documentazione con il codice sorgente, la seconda è quella di cercare, tramite un motore di ricerca, qualcuno che l’abbia già scritto e di riutilizzarlo inserendolo all’interno del tuo progetto, la terza scelta è quella di usare NPM. NPM permette a tutti gli sviluppatori di caricare le loro librerie, così da essere riutilizzate da altri, infatti, puoi cercare le librerie che ti interessano, installarle tramite una singola riga di comando e richiamare subito le funzionalità all’interno del tuo progetto. Un’altra cosa interessante è la gestione delle dipendenze. Supponiamo che il pacchetto per integrare i pagamenti di PayPal funzioni grazie all’uso di un altro pacchetto che si occupa di impostare la richiesta e la risposta in un certo formato, quando andremo ad installare il pacchetto per i pagamenti di PayPal, NPM controllerà se il pacchetto ha dipendenze, e se le trova, installerà automaticamente anche l’altro pacchetto. Infine, se hai bisogno di spostare il tuo progetto su un altro server, non avrai bisogno di spostare anche le librerie installate, ti basterà spostare soltanto il codice sorgente insieme ad un file che contiene tutta la lista dei pacchetti installati nel tuo progetto (package.json), così sul nuovo server basterà lanciare una riga di comando per scaricare tutti i pacchetti.