Introduzione a NPM e i Moduli in Node JS

Corso completo Web Developer - Parte 3 - 3.1

Introduzione

In questo articolo parleremo di cosa sono e come usare i moduli in Node.js e di come gestirli tramite il gestore di pacchetti NPM. Sarà un modulo ricco di concetti interessanti che serviranno come base per i prossimi articoli. Infatti, nel prossimo articolo saremo pronti per esplorare Express, un pacchetto NPM che ci permetterà di creare server web in Node.js!!

Tornando in tema, manteniamo adesso la concentrazione per capire cosa sono e come vengono gestiti i Moduli in Node JS!

I moduli in Node.js non sono nient'altro che files in JavaScript che esportano una funzione, una variabile e/o un oggetto per essere utilizzati in altri file.

Magari ti starai chiedendo perchè scrivere una cosa in un file se ci serve in un altro file.

La risposta è semplice. Nella programmazione si cerca di scrivere una cosa che può essere generica abbastanza da poter essere riutilizzata.

Facciamo un esempio molto basico. Se volessimo scrivere un pezzo di logica che ci serve per calcolare il risultato di 2 + 2 creeresti una funzione chiamata addizione che accetta due parametri a e b e faresti l'addizione di a e b al suo interno.

Non vorremmo mai avere una funzione specifica come questa per esempio:

function sommaDuePiuDue () {
	return 2 + 2;
}

sommaDuePiuDue() // 4

Perchè? Perchè questa funzione è specifica per il calcolo di 2 + 2. Non è generica e non può essere riutilizzata per calcolare 3 + 3 o 4 + 4. Per questo motivo è meglio scrivere una funzione generica che accetta due parametri e fa l'addizione tra di loro.

function addizione(a, b) {
	return a + b;
}

addizione(2, 2) // 4
addizione(3, 3) // 6
addizione(4, 4) // 8

Il risultato non cambia ma se avessimo bisogno di fare un' altra addizione non dovremmo definire una nuova funzione ma possiamo semplicemente riutilizzare addizione e passare parametri differenti.

Questa funzione poi potrebbe servirci in files differenti da quello in cui è stata definita e quindi abbiamo bisogno di un modo per poterla esportare dal file in cui è stata definita ed importarla nel file in cui ci occorre utilizzarla.

Per esportare viene utilizzato il concetto di module.exports. Andiamo a vedere cos'è 🙂

Come funziona Module Exports

Per esportare funzioni, variabili o oggetti da un modulo, dobbiamo utilizzare l'oggetto module.exports. L'oggetto module.exports è un oggetto preposto a contenere valori e funzioni che vogliamo esportare da un determinato file. È come se dicessimo: "Come faccio a trasportare tutti gli oggetti che sono sulla mia scrivania dalla mia stanza a quella di mio fratello? Li metto in una scatola e la porto nella stanza di mio fratello."

Module.exports agisce come la scatola, la mia attuale stanza è il file dal quale voglio esportare questi oggetti e quella di mio fratello è il file in cui voglio importare questi oggetti.

Ad esempio, se volessimo esportare due funzioni che calcolano la somma e il quadrato di due numeri, potremmo avere un file chiamato math.js che potrebbe avere al suo interno le seguenti funzioni:

function sum(a, b) {
  return a + b;
}

function square(x) {
  return x * x;
}

module.exports = {
  sum: sum,
  square: square
};

In questo caso, abbiamo definito due funzioni sum e square, e le abbiamo esportate come proprietà dell'oggetto module.exports.

Per utilizzare queste funzioni in un altro file, dobbiamo utilizzare la funzione require di Node.js.

Facciamo un esempio dove abbiamo un file main.js. Possiamo importare le funzioni ed utilizzarle nel modo seguente:

const math = require('./math');

console.log(math.sum(2, 3)); // output: 5
console.log(math.square(3)); // output: 9

In questo caso, abbiamo richiesto il modulo math.js utilizzando il percorso della cartella corrente "./" e aggiungendo il nome del file "math" senza dover specificare l'estensione ".js", e abbiamo assegnato l'oggetto esportato a una variabile chiamata math. Abbiamo quindi utilizzato le funzioni sum e square come proprietà dell'oggetto math.

Attenzione, la constante che abbiamo creato math (che appunto facciamo riferimento come l'oggetto math) può avere qualsiasi nome e non deve avere lo stesso nome del file che stiamo importando.

Quindi avremmo una situazione nel nostro text editor più o meno come questa:

Fare il require di una cartella

Adesso che abbiamo visto come possiamo esportare un file e come possiamo fare il require di un singolo file, ora la domanda è come facciamo se volessimo importare molteplici file che si trovano in una singola cartella?

In alcuni casi, potremmo voler esportare più funzioni o variabili che magari sono state scritte in diversi file. L'obiettivo è sempre quello di rendere la struttura dei nostri progetti modulare. Infatti la struttura modulare ci permette di riutilizzare il codice e di mantenere la nostra applicazione organizzata e strutturata seguendo una logica che può essere compresa da persone che non sono familiari con il nostro progetto.

Per esempio, supponiamo di avere una cartella chiamata utils che contiene due files: string.js e number.js. Il file string.js contiene una funzione per contare il numero di caratteri in una stringa, mentre il file number.js contiene una funzione per calcolare il quadrato di un numero.

Vogliamo importare queste due funzioni nel nostro file chiamato main.js e utilizzarle per calcolare il numero di caratteri in una stringa e il quadrato di un numero. Come facciamo? Vediamolo insieme.

Per prima cosa, creiamo una cartella chiamata utils e dentro questa cartella creiamo due files: string.js e number.js.

// utils/string.js
function countCharacters(str) {
  return str.length;
}

module.exports = { countCharacters };

// utils/number.js
function square(x) {
  return x * x;
}

module.exports = { square };

Adesso creiamo un file index.js nella stessa cartella utils e importiamo i due files string.js e number.js all'interno di questo file.

// utils/index.js
const string = require('./string');
const number = require('./number');

module.exports = {
  string,
  number
};

Adesso possiamo importare il file index.js all'interno del nostro file main.js e utilizzare le funzioni countCharacters e square come segue:

// main.js
const utils = require('./utils');

console.log(utils.string.countCharacters('Hello World')); // output: 11
console.log(utils.number.square(3)); // output: 9

Se ti starai chiedendo come mai abbiamo fatto il require della cartella utils e siamo riusciti ad utilizzare entrambe le funzioni, countCharacters e square, la risposta è che lo abbiamo fatto grazie alla magia del file index.js che abbiamo a nostra disposizione in JavaScript.

Infatti, quando importiamo una cartella, Node.js cerca automaticamente un file chiamato index.js all'interno della cartella. Se lo trova, lo importa automaticamente. Se non lo trova, Node.js restituisce un errore. Nel nostro caso il file è presente ed a sua volta importa i due files string.js e number.js che contengono le funzioni che vogliamo utilizzare quindi possiamo accedere ed utilizzare le funzioni comodamente importanto una cartella sola.

A questo punto dovremmo avere una situazione del genere:

La struttura della cartella:

Il file string.js

Il file number.js

Il file index.js

Il file main.js

Se domani volessimo aggiungere una nuova funzione nella cartella utils non dovremmo fare altro che aggiungere un nuovo file all'interno della cartella utils e poi importarlo nel file index.js. Nel file main.js avremo già importato la cartella utils e quindi avremo subito a disposizione anche la nuova funzione aggiunta. 🙂

Introduzione a NPM

NPM (Node Package Manager) è il gestore di pacchetti ufficiale per Node.js.

NPM è molto importante perchè ci consente di installare, aggiornare e rimuovere pacchetti Node.js da un repository centrale dove tanti programmatori come noi creano tanti piccoli/medi/grandi software e li mettono a disposizione per essere utilizzati.

Ci sono due modi principali per utilizzare NPM:

  1. come strumento per la riga di comando per la gestione dei pacchetti (installare, aggiornare e rimuovere pacchetti)
  2. come una libreria di pacchetti Node.js (una sorta di App/Google Store per i pacchetti di JavaScript)

Questa è una breve infarinatura di NPM. C'è un altro piccolo pezzo di teoria che dobbiamo capire prima che possiamo passare alla pratica. Questo pezzo mancante è il file package.json. Andiamolo a scoprire!

Il file package.json

Quando sviluppiamo un progetto in JavaScript nella maggior parte dei casi avremo delle “dependency” esterne. Ogni qual volta ascolti il termine di dependency esterna si fa riferimento a del codice che vive in un pacchetto esterno al nostro progetto. Ad esempio un codice preso da un pacchetto NPM è considerato essere una dependency esterna.

Quando si inizia ad installare molteplici pacchetti o dependency esterne è importante tenere traccia dei pacchetti che stiamo utilizzando e delle loro versioni. Questo perchè a seconda del pacchetto che stiamo utilizzando potrebbero esserci degli aggiornamenti e delle versioni diverse che potrebbero rendere il funzionamento del pacchetto che stiamo utilizzando non più compatibile con il nostro progetto o con il codice che abbiamo scritto per utilizzarlo e magari necessita di una revisione.

Per evitare problemi inaspettati dovuti ad aggiornamenti automatici di versioni, è importante sapere quale versione del pacchetto stiamo utilizzando e se necessario aggiornare il pacchetto manualmente.

Dove possiamo tenere traccia di queste versioni? Nel file package.json ovviamente! 😃

Il file package.json è un file importante che non soltanto contiene informazioni sulle dipendenze esterne che installiamo (queste sono in realtà informazioni vitali) ma contiene anche informazioni più generali del nostro progetto. Può essere visto un pò come una carta d'identità che contiene informazioni identificative come ad esempio il nome del progetto, il nome dell'autore, il tipo di licenza se ne ha una, oltre a tutte le informazioni sulle dipendenze esterne di cui abbiamo già parlato.

Per creare questo file è semplicissimo, ti basta eseguire il comando npm init nella cartella del progetto e verrai guidato attraverso una serie di domande per impostare le informazioni corrette del tuo progetto. Basta seguire l'output nel terminale ed inserire le informazioni che ci richiede.

Una volta creato il file package.json, possiamo utilizzarlo per installare tutte le dipendenze del nostro progetto.

Nella prossima parte faremo un esempio con l'installazione di un pacchetto che genera battute comiche in inglese in modo casuale. È un pacchetto base e molto semplice da utilizzare che non richiede alcuna configurazione particolare.

Pronto/a? Proseguiamo! 😃

Installazione di un pacchetto dalla repository NPM

Creiamo una nuova cartella che chiameremo npm_example e facciamo il cd al suo interno.

Ora che ci troviamo all'interno della cartella npm_example eseguiamo il comando npm init per creare il file package.json e rispondiamo alle domande che ci vengono poste. Ognuna di queste domande ha un valore di default che se non vogliamo modificare possiamo semplicemente premere il tasto invio per confermare il valore di default. Come puoi vedere nell'immagine qui sotto, ho modificato solo la description e author e per il resto ho premuto semplicemente invio. Avrei anche potuto lasciare tutti i campi di default.

Ora apriamo il nostro progetto in VS Code e analizziamo il file package.json che abbiamo appena creato.

Noterai che ha un formato JSON (JavaScript Object Notation) e che contiene le seguenti informazioni:

  • name: il nome del progetto (se abbiamo lasciato quello di default verrà utilizzato automaticamente lo stesso nome della cartella in cui ci troviamo)
  • version: la versione del progetto (se abbiamo lasciato quello di default verrà utilizzato automaticamente il valore 1.0.0)
  • description: la descrizione del progetto (puoi notare che questa è la descrizione che ho dato mentre ho creato il file package.json)
  • main: il file principale del progetto (se abbiamo lasciato quello di default verrà utilizzato automaticamente il valore index.js)
  • scripts: gli script che possiamo eseguire (di default abbiamo soltanto uno chiamato test che per ora non c'interessa)
  • author: l'autore del progetto (puoi notare che è stato riportato il mio nome perchè è quello che ho scritto durante la creazione del file package.json)
  • license: la licenza del progetto (se abbiamo lasciato quello di default verrà utilizzato automaticamente il valore ISC)

Le risposte che diamo durante la creazione del file package.json non sono necessariamente definitive. Nel caso in cui tu avessi fatto degli errori di battitura ad esempio o comunque vuoi modificare qualcosa, puoi farlo tranquillamente in modo manuale modificando il file una volta creato.

Ora siamo pronti ad installare il nostro primo pacchetto dalla libreria di NPM 🥳

Per installare un pacchetto dalla libreria di NPM basta eseguire il comando npm install <nome_pacchetto> nella cartella del progetto. Nel nostro caso installeremo un pacchetto chiamato one-liner-joke che genera battute comiche in inglese in modo casuale.

È importante scrivere il nome nel modo corretto seguendo ogni singolo carattere del nome del pacchetto altrimenti potremmo installare erroneamente un pacchetto diverso o semplicemente ottenere un errore.

Quindi proseguiamo con l'installazione del pacchetto eseguendo il comando npm install one-liner-joke nella cartella del progetto.

Se ti dirà "added 1 package, ...." vuol dire che hai installato correttamente il pacchetto.

Andiamo a vedere cos'è successo nella cartella. Ci sono stati tre cambiamenti importanti:

  • è stata creata una nuova cartella chiamata node_modules che contiene tutti i pacchetti che abbiamo installato
  • è stato creato un nuovo file chiamato package-lock.json che contiene informazioni sulle dipendenze del nostro progetto
  • è stato modificato il file package.json aggiungendo una nuova chiave chiamata dependencies che contiene tutte le dipendenze del nostro progetto

Ti spiegherò queste cosa sono in un attimo. Ora voglio farti vedere prima come utilizzare il pacchetto! 😃

Creiamo un file index.js e scriviamo il seguente codice:

const oneLinerJoke = require('one-liner-joke');

Con questa linea di codice stiamo facendo il require del pacchetto. Ora come facciamo a sapere come utilizzarlo? 😕

Qui entra in gioco l'importanta delle documentazioni nella programmazione. Infatti il modo più semplice per capire come utilizzare il pacchetto è leggere le sue istruzioni d'uso.

Ogni pacchetto NPM (ed ogni progetto) che si rispetti ha una sua documentazione. Basta andare sulla pagina di questo pacchetto NPM e leggere la documentazione. Questo è il link del pacchetto: https://www.npmjs.com/package/one-liner-joke

Noteremo che tra le varie informazioni c'è una sezione Usage che contiene le istruzioni. Qui ci dice che per generare una battuta casuale dobbiamo utilizzare il metodo getRandomJoke().

Ci sono altre informazioni come ad esempio la versione del pacchetto, il numero dei downloads settimanali che questo pacchetto riceve, il tipo di licenza ecc. ecc.

Quindi ora che sappiamo come utilizzarlo proviamolo nel nostro file index.js:

const oneLinerJoke = require('one-liner-joke');

const randomJoke = oneLinerJoke.getRandomJoke();

console.log(randomJoke);

Eseguiamo il file index.js dal terminale con node index.js e questo è il risultato che dovremmo ottenere:

Come vedi ci ha restituito un oggetto con due proprietà: body e tags. La proprietà body contiene la battuta casuale mentre la proprietà tags contiene un array di tag che descrivono la battuta.

Se volessimo ottenere soltanto la battuta possiamo utilizzare la proprietà body:

const oneLinerJoke = require('one-liner-joke');

const randomJoke = oneLinerJoke.getRandomJoke();

console.log(randomJoke.body);

Ovviamente la battuta che otterremo adesso sarà diversa dalla precedente appunto perchè genera una battuta casuale ogni volta che utilizziamo la funzione getRandomJoke 😃

Ad esempio proviamo a generate 10 battute diverse utilizzando un ciclo for e stampiamole tutte nel terminale!

const oneLinerJoke = require('one-liner-joke');

for (let i = 0; i < 10; i++) {
  const randomJoke = oneLinerJoke.getRandomJoke();
  console.log(i + 1, randomJoke.body);
}

Se ti stai chiedendo come mai abbiamo utilizzato i + 1 invece di i ti spiego subito. Il ciclo for parte dall'indice (i) 0 e quindi la prima volta che viene eseguito i è uguale a 0. Per iniziare la lista dal numero 1 anzichè dal numero zero aggiungo 1 all'indice 1 di modo che nel console log stampi 1 invece di 0 e cosí via.

Se ti stai anche chiedendo perchè abbiamo messo i + 1 prima di randomJoke.body è per stampare nella console una lista numerata. 😃 Infatti al console.log puoi passare più argomenti e questi verranno separati da uno spazio quindi passando il primo argomento che rappresenta il numero ed il secondo che rappresenta la battuta otterremo ogni log con il formato "NUMERO - BATTUTA".

Per ricapitolare, abbiamo installato un pacchetto attraverso il comando npm install NOME PACCHETTO, abbiamo fatto il require del pacchetto nel nostro file index.js, abbiamo letto la documentazione per capire come utilizzare il pacchetto e finalmente siamo riusciti ad utilizzarlo nel nostro codice. In questo modo siamo riusciti ad utilizzare un pacchetto NPM scritto da un altro programmatore nel nostro programma. Figo, no? 😃 Infatti quando si sviluppano applicazioni complesse non si sviluppa mai tutto da zero ma vengono sempre riutilizzati codici già esistenti in aggiunta al nostro codice. Come se stessimo creando un lego con pezzi esistenti. L'esempio che viene spesso utilizzato è di "non reinventare sempre la ruota".

Conclusione

In questo articolo abbiamo esplorato come utilizzare i moduli in Node.js e come gestirli tramite NPM.

Abbiamo visto come esportare funzioni, variabili ed oggetti da un modulo, come richiedere una cartella con più moduli, come creare il file package.json e come utilizzare un pacchetto Node.js proveniente dalla repository NPM.

Nella prossima lezione, introdurremo Express, un pacchetto mooolto importante che viene molto utilizzato per creare servers backend in Node.js. 🚀