035.1 Lezione 1
Certificazione: |
Web Development Essentials |
---|---|
Versione: |
1.0 |
Argomento: |
035 Programmazione Server con Node.js |
Obiettivo: |
035.1 Fondamenti di Node.js |
Lezione: |
1 di 1 |
Introduzione
Node.js è un ambiente runtime JavaScript che consente di eseguire codice JavaScript sui server web — il cosiddetto web backend (lato server) — invece di usare un secondo linguaggio come Python o Ruby per programmi lato server. Il linguaggio JavaScript è già usato nel moderno lato frontend delle applicazioni web, interagendo con l’HTML e il CSS dell’interfaccia con cui l’utente interagisce in un browser web. Usare Node.js in tandem con JavaScript nel browser offre la possibilità di un solo linguaggio di programmazione per l’intera applicazione.
Il motivo principale dell’esistenza di Node.js è il modo in cui gestisce più connessioni simultanee nel back-end. Uno dei modi più comuni in cui un server di applicazioni Web gestisce le connessioni è l’esecuzione di più processi. Quando si apre un’applicazione desktop nel computer, viene avviato un processo che utilizza molte risorse. Ora pensa a quando migliaia di utenti stanno facendo la stessa cosa in una grande applicazione web.
Node.js evita questo problema usando un design chiamato event loop, che è un ciclo interno che controlla continuamente i compiti in arrivo da calcolare. Grazie all’uso diffuso di JavaScript e all’ubiquità delle tecnologie web, Node.js ha visto un’enorme adozione in applicazioni piccole e grandi. Ci sono anche altre caratteristiche che hanno aiutato Node.js a essere ampiamente adottato, come l’elaborazione asincrona e non bloccante di input/output (I/O), che è spiegata più avanti in questa lezione.
L’ambiente Node.js utilizza un motore JavaScript per interpretare ed eseguire il codice JavaScript sul lato server o sul lato client. In queste condizioni, il codice JavaScript che il programmatore scrive viene analizzato e compilato just-in-time per eseguire le istruzioni macchina generate dal codice JavaScript originale.
Note
|
Man mano che procederai con queste lezioni su Node.js, potrai notare che il JavaScript di Node.js non è esattamente lo stesso di quello che viene eseguito sul browser (che segue le specifiche ECMAScript), ma è comunque abbastanza simile. |
Per Iniziare
Questa sezione e gli esempi seguenti presuppongono che Node.js sia già installato sul sistema operativo Linux e che l’utente abbia già delle competenze di base come l’esecuzione di comandi nel terminale.
Per eseguire i seguenti esempi, crea una directory di lavoro chiamata node_examples
. Apri un prompt del terminale e digita node
. Se hai installato correttamente Node.js, ti presenterà un prompt >
dove puoi testare i comandi JavaScript in modo interattivo. Questo tipo di ambiente è chiamato REPL, che sta per “read, evaluate, print, and loop” Digita il seguente input (o qualche altra istruzione JavaScript) al prompt >
. Premi il tasto Invio dopo ogni riga, e l’ambiente REPL restituirà i risultati delle sue azioni:
> let array = ['a', 'b', 'c', 'd']; undefined > array.map( (element, index) => (`Element: ${element} at index: ${index}`)); [ 'Element: a at index: 0', 'Element: b at index: 1', 'Element: c at index: 2', 'Element: d at index: 3' ] >
Il codice è stato scritto usando la sintassi ES6, che offre una funzione map per iterare sull’array e visualizzare i risultati usando modelli di stringa. Puoi scrivere praticamente qualsiasi comando che sia valido. Per uscire dal terminale Node.js, digita .exit
, ricordandoti di includere il punto iniziale.
Per script e moduli più lunghi, è più conveniente usare un editor di testo come VS Code, Emacs o Vim. Puoi salvare le due righe di codice appena mostrate (con una piccola modifica) in un file chiamato start.js
:
let array = ['a', 'b', 'c', 'd'];
array.map( (element, index) => ( console.log(`Element: ${element} at index: ${index}`)));
Poi puoi eseguire lo script dalla shell per produrre gli stessi risultati di prima:
$ node ./start.js Element: a at index: 0 Element: b at index: 1 Element: c at index: 2 Element: d at index: 3
Prima di immergerci un po' di più nel codice, facciamo una panoramica di come funziona Node.js, usando il suo ambiente di esecuzione a thread singolo e il ciclo degli eventi (event loop).
Ciclo degli Eventi e Thread Singolo
È difficile dire quanto tempo impiegherà un programma Node.js per gestire una richiesta. Alcune richieste possono essere brevi — forse solo un ciclo attraverso le variabili in memoria e la loro restituzione — mentre altre possono necessitare di attività che richiedono tempo, come l’apertura di un file sul sistema o l’esecuzione di una query su un database e l’attesa dei risultati. Come fa Node.js a gestire questa incertezza? L'event loop è la risposta.
Immagina uno chef che svolge più compiti. Cuocere una torta è un compito che richiede molto tempo al forno. Lo chef non rimane lì ad aspettare che la torta sia pronta per mettresi a preparare il caffè. Invece, mentre il forno cuoce la torta, il cuoco prepara il caffè e svolge altri compiti in parallelo. Ma il cuoco controlla sempre se è il momento giusto per spostare l’attenzione su un compito specifico (preparare il caffè), o per tirare fuori la torta dal forno.
Il ciclo degli eventi è come lo chef che è costantemente consapevole delle attività circostanti. In Node.js, un “event-checker” controlla sempre le operazioni che si sono completate o sono in attesa di essere processate dal motore JavaScript.
Usando questo approccio un’operazione asincrona e lunga non blocca altre operazioni veloci che vengono dopo. Questo perché il meccanismo del ciclo degli eventi controlla sempre se quel compito lungo, come un’operazione di I/O, è già stato eseguito. In caso contrario, Node.js può continuare ad elaborare altri compiti. Una volta che il compito in background è completato, i risultati vengono restituiti e l’applicazione che usa Node.js può utilizzare una funzione di trigger (callback) per elaborare ulteriormente l’output.
Poiché Node.js evita l’uso di thread multipli, come fanno altri ambienti, è chiamato un ambiente single-threaded, e un approccio non bloccante è della massima importanza. Questo è il motivo per cui Node.js utilizza un ciclo di eventi. Per compiti ad alta intensità di calcolo, Node.js non è comunque tra gli strumenti migliori: ci sono altri linguaggi di programmazione e ambienti che affrontano questi problemi in modo più efficiente.
Nelle sezioni seguenti daremo uno sguardo più da vicino alle funzioni di callback. Per ora, ti basti sapere che tali funzioni sono trigger che vengono eseguiti al completamento di un’operazione predefinita.
Moduli
È buona pratica suddividere funzionalità complesse e parti di codice esteso in parti più piccole. Questa modularizzazione aiuta a organizzare meglio la base di codice, ad astrarre le implementazioni e a evitare complicati problemi di ingegneria software. Per soddisfare queste necessità, i programmatori confezionano blocchi di codice sorgente perchè siano elaborati da altre parti interne o esterne di codice.
Consideriamo l’esempio di un programma che calcola il volume di una sfera. Apri il tuo editor di testo e crea un file chiamato volumeCalculator.js
contenente il seguente JavaScript:
const sphereVol = (radius) => {
return 4 / 3 * Math.PI * radius
}
console.log(`A sphere with radius 3 has a ${sphereVol(3)} volume.`);
console.log(`A sphere with radius 6 has a ${sphereVol(6)} volume.`);
Ora, esegui il file usando Node:
$ node volumeCalculator.js A sphere with radius 3 has a 113.09733552923254 volume. A sphere with radius 6 has a 904.7786842338603 volume.
È stata usata una semplice funzione per calcolare il volume di una sfera, in base al suo raggio. Immaginiamo di dover calcolare anche il volume di un cilindro, di un cono, e così via: notiamo subito che queste funzioni specifiche devono essere aggiunte al file volumeCalculator.js
, che può diventare un’enorme collezione di funzioni. Per organizzare meglio la struttura, possiamo suddividere il tutto in pacchetti di codice separato attraverso la creazione di moduli.
Per farlo, crea un file separato chiamato polyhedrons.js
realizzato nel seguente modo:
const coneVol = (radius, height) => {
return 1 / 3 * Math.PI * Math.pow(radius, 2) * height;
}
const cylinderVol = (radius, height) => {
return Math.PI * Math.pow(radius, 2) * height;
}
const sphereVol = (radius) => {
return 4 / 3 * Math.PI * Math.pow(radius, 3);
}
module.exports = {
coneVol,
cylinderVol,
sphereVol
}
Ora, nel file volumeCalculator.js
, elimina il vecchio codice e sostituiscilo con:
const polyhedrons = require('./polyhedrons.js');
console.log(`A sphere with radius 3 has a ${polyhedrons.sphereVol(3)} volume.`);
console.log(`A cylinder with radius 3 and height 5 has a ${polyhedrons.cylinderVol(3, 5)} volume.`);
console.log(`A cone with radius 3 and height 5 has a ${polyhedrons.coneVol(3, 5)} volume.`);
E poi esegui l’ambiente Node.js:
$ node volumeCalculator.js A sphere with radius 3 has a 113.09733552923254 volume. A cylinder with radius 3 and height 5 has a 141.3716694115407 volume. A cone with radius 3 and height 5 has a 47.12388980384689 volume.
Nell’ambiente Node.js, ogni file di codice sorgente è considerato un modulo, ma il termine “module” in Node.js indica un pacchetto di codice strutturato come nell’esempio precedente. Usando i moduli, abbiamo astratto le funzioni del volume dal file principale, volumeCalculator.js
, riducendo così la sua dimensione e rendendo più facile applicare i test unitari, che sono una buona pratica quando si sviluppano applicazioni del mondo reale.
Ora che sappiamo come si usano i moduli in Node.js, possiamo usare uno degli strumenti più importanti: il Node Package Manager (NPM).
Uno dei compiti principali di NPM è quello di gestire, scaricare e installare moduli esterni nel progetto o nel sistema operativo. Puoi inizializzare un repository di node con il comando npm init
.
NPM porrà le domande predefinite sul nome del tuo repository, la versione, la descrizione e così via. Puoi saltare questi passaggi usando npm init --yes
, e il comando genererà automaticamente un file package.json
che descrive le proprietà del tuo progetto/modulo.
Apri il file package.json
nel tuo editor di testo preferito e vedrai un file JSON contenente proprietà come parole chiave, comandi di script da usare con NPM, un nome e così via.
Una di queste proprietà indica le dipendenze che sono installate nel tuo repository locale. NPM aggiungerà il nome e la versione di queste dipendenze in package.json
, insieme a package-lock.json
, un altro file usato come fallback da NPM nel caso in cui il file package.json
fallisca.
Digita quanto segue sul tuo terminale:
$ npm i dayjs
L’argomento i
è una abbreviazione che sta per install
. Se sei connesso ad Internet, NPM cercherà un modulo chiamato dayjs
nel repository remoto di Node.js, scaricherà il modulo e lo installerà localmente. NPM aggiungerà anche questa dipendenza ai tuoi file package.json
e package-lock.json
. Ora puoi vedere che c’è una cartella chiamata node_modules
, che contiene il modulo installato insieme ad altri moduli se necessari. La cartella node_modules
contiene il codice effettivo che verrà usato quando la libreria verrà importata e chiamata. Tuttavia, questa cartella non viene salvata nei sistemi di versioning che usano Git, poiché il file package.json
fornisce tutte le dipendenze utilizzate. Un altro utente può prendere il file package.json
ed eseguire semplicemente npm install
nella propria macchina, dove NPM creerà una cartella node_modules
con tutte le dipendenze nel package.json
, evitando così il controllo di versione per le migliaia di file disponibili sul repository NPM.
Ora che il modulo dayjs
è installato nella directory locale, apri la console di Node.js e digita le seguenti righe:
const dayjs = require('dayjs');
dayjs().format('YYYY MM-DDTHH:mm:ss')
Il modulo dayjs
è caricato con la parola chiave require
. Quando viene chiamato un metodo dal modulo, la libreria prende il datetime corrente del sistema e lo restituisce nel formato specificato:
2020 11-22T11:04:36
Questo è lo stesso meccanismo usato nell’esempio precedente, dove il runtime di Node.js carica la funzione di terze parti nel codice.
Funzionalità del Server
Poiché Node.js controlla il back-end delle applicazioni web, uno dei suoi compiti principali è quello di gestire le richieste HTTP.
Ecco un riassunto di come i server web gestiscono le richieste HTTP in entrata. La funzionalità del server è quella di ascoltare le richieste, determinare il più rapidamente possibile la risposta di cui ciascuno ha bisogno, e restituire tale risposta al mittente della richiesta. Questa applicazione deve ricevere una richiesta HTTP in entrata innescata dall’utente, analizzare la richiesta, eseguire il calcolo, generare la risposta e rimandarla indietro. Un modulo HTTP come Node.js viene utilizzato perché semplifica questi passaggi, permettendo al programmatore web di concentrarsi sull’applicazione.
Considera il seguente esempio che implementa questa funzionalità di base:
const http = require('http');
const url = require('url');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
const queryObject = url.parse(req.url,true).query;
let result = parseInt(queryObject.a) + parseInt(queryObject.b);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(`Result: ${result}\n`);
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Salva questi contenuti in un file chiamato basic_server.js
ed eseguilo attraverso un comando node
. Il terminale che esegue Node.js mostrerà il seguente messaggio:
Server running at http://127.0.0.1:3000/
Poi visita la seguente URL nel tuo browser web: http://127.0.0.1:3000/numbers?a=2&b=17
Node.js esegue un server web nel tuo computer e usa due moduli: http
e url
. Il modulo http
imposta un server HTTP di base, elabora le richieste web in arrivo e le passa al nostro semplice codice applicativo. Il modulo URL analizza gli argomenti passati nella URL, li converte in un formato intero ed esegue l’operazione di addizione. Il modulo http
invia quindi la risposta come testo al browser web.
In una vera applicazione web, Node.js è comunemente usato per elaborare e recuperare dati, di solito da un database, e restituire le informazioni elaborate al front-end per visualizzarle. Ma l’applicazione di base in questa lezione mostra concisamente come Node.js faccia uso di moduli per gestire le richieste web come un server web.
Esercizi Guidati
-
Quali sono le ragioni per usare moduli invece di scrivere semplici funzioni?
-
Perché l’ambiente Node.js è diventato così popolare? Citane una caratteristica.
-
Qual è lo scopo del file
package.json
? -
Perché non è raccomandato salvare e condividere la cartella
node_modules
?
Esercizi Esplorativi
-
Come puoi eseguire applicazioni Node.js sul tuo computer?
-
Come si possono delimitare i parametri da analizzare nella URL all’interno del server?
-
Indica uno scenario in cui un compito specifico potrebbe essere un collo di bottiglia per un’applicazione Node.js.
-
Come implementeresti un parametro per moltiplicare o sommare i due numeri nell’esempio del server?
Sommario
Questa lezione fornisce una panoramica dell’ambiente Node.js, le sue caratteristiche e come può essere usato per implementare semplici programmi. Questa lezione include i seguenti concetti:
-
Cos’è Node.js e perché viene usato.
-
Come eseguire programmi Node.js usando la linea di comando.
-
I cicli di eventi e il singolo thread.
-
I moduli.
-
Node Package Manager (NPM).
-
Funzionalità del server.
Risposte agli Esercizi Guidati
-
Quali sono le ragioni per usare moduli invece di scrivere semplici funzioni?
Optando per i moduli invece delle funzioni convenzionali, il programmatore crea una base di codice più semplice da leggere e mantenere e per la quale scrivere test automatici.
-
Perché l’ambiente Node.js è diventato così popolare? Citane una caratteristica.
Una ragione è la flessibilità del linguaggio JavaScript, che era già ampiamente utilizzato nel front-end delle applicazioni web. Node.js permette l’uso di un solo linguaggio di programmazione in tutto il sistema.
-
Qual è lo scopo del file
package.json
?Questo file contiene metadati per il progetto, come il nome, la versione, le dipendenze (librerie) e così via. Dato un certo file
package.json
, altre persone possono scaricare e installare le stesse librerie ed eseguire test nello stesso modo in cui lo ha fatto il creatore originale. -
Perché non è raccomandato salvare e condividere la cartella
node_modules
?La cartella
node_modules
contiene le implementazioni delle librerie disponibili in repository remoti. Quindi il modo migliore per condividere queste librerie è quello di indicarle nel filepackage.json
e poi usare NPM per scaricare queste librerie. Questo metodo è più semplice e privo di errori, perché non è necessario rintracciare e mantenere le librerie localmente.
Risposte agli Esercizi Esplorativi
-
Come puoi eseguire applicazioni Node.js sul tuo computer?
Puoi eseguirle digitando
node PATH/FILE_NAME.js
nella linea di comando nel tuo terminale, cambiandoPATH
con il percorso del tuo file Node.js e cambiandoFILE_NAME.js
con il nome del file scelto. -
Come si possono delimitare i parametri da analizzare nella URL all’interno del server?
Il carattere ampersand
&
è usato per delimitare questi parametri, in modo che possano essere estratti e analizzati nel codice JavaScript. -
Indica uno scenario in cui un compito specifico potrebbe essere un collo di bottiglia per un’applicazione Node.js.
Node.js non è un buon ambiente in cui eseguire processi intensivi per la CPU perché usa un singolo thread. Uno scenario di calcolo numerico potrebbe rallentare e bloccare l’intera applicazione. Se è necessaria una simulazione numerica, è meglio usare altri strumenti.
-
Come implementeresti un parametro per moltiplicare o sommare i due numeri nell’esempio del server?
Usa un operatore ternario o una condizione if-else per controllare un parametro aggiuntivo. Se il parametro è la stringa
mult
, questa restituisce il prodotto dei numeri, altrimenti restituisce la somma. Sostituisci il vecchio codice con quello qui sotto. Riavvia il server dalla linea di comando premendo kbd:[Ctrl+C] e rilanciando il comando per riavviare il server. Ora prova la nuova applicazione visitando l’URLhttp://127.0.0.1:3000/numbers?a=2&b=17&operation=mult
nel tuo browser. Se ometti o cambi l’ultimo parametro, il risultato dovrebbe essere la somma dei numeri.let result = queryObject.operation == 'mult' ? parseInt(queryObject.a) * parseInt(queryObject.b) : parseInt(queryObject.a) + parseInt(queryObject.b);