Guida per neofiti a Node.js
In questa guida mostriamo come iniziare a usare Node.js, dal principio.
La maggior parte delle guide che si trovano in rete, coprono soltanto aspetti specifici di Node.js senza trattare tutto ciò che viene prima. Dall’installazione fino al primo utilizzo.
Cos’è Node.js?
Tra i neofiti aleggia molta confusione, scaturita dal fatto di non capire esattamente cosa sia Node.js.
E’ importante capire che Node.js non è un webserver. Non può funzionare “da solo” come Apache. Non esistono file di configurazione. Se volete si comporti come un server HTTP, è necessario scriverlo il server HTTP. (Con l’aiuto di librerie e altri componenti già pre-esistenti all’interno di Node). Node.js è un altro modo di eseguire codice sul vostro computer. Basato su un motore javascript che esegue codice in runtime.
Installare Node.js
Node.js è facile da installare. Sia che usiate Mac, Windows o Linux, son presenti i pacchetti d’installazione nella sezione Download del sito ufficiale.
Ho installato Node e adesso?
Una volta installato è possibile accedere alla console interattiva di Node, semplicemente con il comando “node” lanciato su Terminale, senza ulteriori argomenti. Una volta entrati sulla console di Node, è possibile eseguirvi direttamente sopra codice Javascript.
$ node > console.log('Ciao Mondo!'); Ciao Mondo! undefined |
Nell’esempio sopra ho scritto nella shell console.log("Ciao Mondo!");
e dato Invio. Node esegue il codice e possiamo vedere il messaggio di log scritto. Stampa anche “undefined” perchè la console di node stampa ogni valore restituito dalle istruzioni, e “console.log()” non restituisce nulla appunto.
L’altro modo per eseguire comandi su Node è fornendogli un file javascript da eseguire.
Nella stragrande maggioranza dei casi si usa fare così.
Dunque creiamo un file ciao.js
e scriviamoci dentro:
console.log("Ciao Mondo!"); |
Salviamo e andiamo ad eseguire da terminale il file con node:
node ciao.js |
Il risultato sarà la stampa su console di Ciao Mondo!
.
File I/O – Facciamo qualcosa di utile
Eseguire codice javascript nativo è divertente, ma non propriamente utile. Motivo per cui Node.js include anche un insieme di potenti librerie (chiamati moduli) utili in scenari reali. In questo primo esempio, mostriamo come fare il parsing di un file testuale.
Il file testuale chiamiamolo esempio.txt e mettiamoci dentro qualcosa del tipo:
2013-08-09T13:50:33.166Z A 2 2013-08-09T13:51:33.166Z B 1 2013-08-09T13:52:33.166Z C 6 2013-08-09T13:53:33.166Z B 8 2013-08-09T13:54:33.166Z B 5 |
Ogni messaggio contiene una data, una lettera e un valore. Quel che si vuol fare è sommare i valori associati ad ogni lettera.
La prima cosa da fare è leggere il contenuto del file:
parser.js
// Carichiamo il modulo filesystem (fs) var fs = require('fs'); // Leggiamo il contenuto del file in memoria. fs.readFile('esempio.txt', function (err, logData) { // Se si verifica un errore, lanciandolo, lo mostreremo e fermeremo l'esecuzione dell'app. if (err) throw err; // logData è un Buffer, convertiamolo in Stringa. var text = logData.toString(); }); |
Fortunatamente Node.js rendere l’I/O di un file realmente facile con il modulo pre-installato fs (filesystem). Il modulo fs ha una funzione chiamata readFile che prendere come parametri il percorso al file e una funzione di callback. La funzione di callback verrà invocata quando il file sarà letto completamente. I dati del file arrivano sottoforma di buffer, sostanzialmente un array di byte. Possiamo convertirlo in stringa semplicemente usando la funzione .toString()
.
Andiamo ad aggiungere il tutto al file parser.js. Si tratta per lo più di codice Javascript, non andremo nel dettaglio.
// Carichiamo il modulo filesystem (fs) var fs = require('fs'); // Leggiamo il contenuto del file in memoria. fs.readFile('esempio.txt', function (err, logData) { // Se si verifica un errore, lanciandolo, lo mostreremo e fermeremo l'esecuzione dell'app. if (err) throw err; // logData è un Buffer, convertiamolo in Stringa. var text = logData.toString(); }); var results = {}; // Spezziamo il file in linee. var lines = text.split('\n'); lines.forEach(function(line) { var parts = line.split(' '); var letter = parts[1]; var count = parseInt(parts[2]); if(!results[letter]) { results[letter] = 0; } results[letter] += parseInt(count); }); console.log(results); // { A: 2, B: 14, C: 6 } }); |
Adesso, quando passeremo questo script come argomento al comando “node”, eseguirà il codice, farà il parsing del file, stamperà il risultato ed uscirà.
$ node parser.js { A: 2, B: 14, C: 6 } |
E’ possibile usare Node.js per operazioni di scripting proprio in questo modo. E’ più facile e rappresenta una valida alternativa agli script bash.
Callbacks Asincrone
Come visto nel precedente esempio, un pattern tipico in Node.js è quello di usare le callbacks asincrone. In cui si chiede di eseguire qualcosa e quando l’operazione viene completata, la funzione di callback viene invocata. Questo perchè Node è Single-Threaded. Nel mentre si aspetta che la funzione di callback venga invocata, Node può effettuare altre operazioni invece di bloccarsi finchè la richiesta non viene completata.
Questo è molto importante per i web server. E’ abbastanza comune nelle applicazioni web moderne accedere a un database. E mentre si aspetta che il database restituisca dei risultati, Node può processare altre richieste. Questo permette di gestire migliaia di richieste concorrenti con un overhead minimo, se paragonato alla creazione di un singolo thread per ogni connessione.
Server HTTP
Come detto in precedenza, Node non fa nulla “uscito dalla scatola”. Tuttavia i moduli presenti semplificano estremamente la creazione di app di ogni genere.
Ad esempio, uno dei moduli, chiamato http, rende facile la creazione di un server HTTP di base (Tra l’altro è l’esempio presente sulla homepage di Node.js).
web_server.js:
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(8080); console.log('Server running on port 8080.'); |
Quando dico “di base”, intendo che questo non è un server HTTP completo. Non può servire file HTML o immagini. Non importa ciò che richiedete, restituirà sempre “Hello World”. Per vederlo è sufficiente eseguirlo node web_server.js
e andare su http://localhost:8080 .
E’ possibile notare una cosa differente rispetto agli esempi visti prima. L’applicazione una volta eseguita non esce più. Questo perchè avete creato un server e l’applicazione continuerà a girare e rispondere alle richieste finchè voi stessi non la chiudete.
Se voleste trasformare quest’app in un server completo, dovreste controllare cosa viene richiesto, leggere il file appropriato, e inviare il contenuto. La buona notizia è che già qualcuno ha fatto il grosso del lavoro per semplificarci la cosa.
Express
Express è un framework che rende semplice la creazione di web server e siti web.
La prima cosa da fare è installarlo. Per farlo, ci basta sapere che quando installiamo node, assieme viene installato anche il gestore di pacchetti, chiamato “npm” (Node Package Manager). Questo strumento ci da l’accesso ad un’enorme collezione di moduli creati dalla community. Uno di questi moduli è proprio Express.
Ci basta recarci nella directory in cui vogliamo piazzare l’app e lanciare il comando npm install express
:
$ cd /my/app/location $ npm install express |
Quando installi un modulo con npm, verrà messo nella cartella node_modules all’interno della directory dell’applicazione. Ora sarà possibile richiederlo come un qualunque modulo pre-installato.
Ora andiamo a creare un server statico che fornisca i file richiesti usando Express.
static_file_server.js
var express = require('express'), app = express(); app.use(express.static(__dirname + '/public')); app.listen(8080); |
Ora avete un server statico. Qualsiasi cosa mettiate nella cartella /public può essere richiesto dal browser e mostrato. HTML, immagini, qualsiasi cosa.
Express naturalmente è capace di ben altro. Vi invito a studiare la documentazione e in base alle esigenze, sperimentare con questo potente strumento.
NPM (Node Package Manager)
Ci sono migliaia di moduli disponibili che rispondono praticamente a qualsiasi tipo d’esigenza possa nascere. Cercate quindi di consultare la repository di NPM prima di reinventare la ruota. Non è insolito trovare applicazioni Node.js con decine di dipendenze esterne.
Nella sezione precedente abbiamo visto come installare manualmente Express. Se avete molte dipendenze, non è un buon modo di agire e gestirle. Motivo per cui npm fa uso di un file chiamato package.json:
{ "name" : "MyApplication", "version" : "0.0.1", "dependencies" : { "express" : "3.3.x" } } |
Un file package.json contiene una panoramica della vostra applicazione. Ci sono molti campi disponibili. La sezione sulle dipendenze descrive il nome e la versione del modulo che volete installare. Nel caso sopra, si accetta qualsiasi versione di Express 3.3. E’ possibile elencare un numero arbitrario di dipendenze in questa sezione.
Ora, invece di installare separatamente ogni dipendenza, possiamo eseguire un singolo comando e installarle tutte assieme.
$ npm install |
Quando eseguite questo comando, npm cercherà nella cartella corrente un file package.json. Se lo trova, installerà ogni dipendenza listata.
Organizzazione del codice
Fin’ora abbiamo utilizzato un singolo file, che non è una soluzione che permette un elevato tasso di manutenzione. Nella maggior parte delle applicazioni il codice verrà separato in diversi file. Non ci sono dei veri e propri standard che specificano dove e come si organizzino i file. Questo non è Ruby. Non ci sono viste nè controllers. Si può agire nel modo che si preferisce.
Rivediamo e riorganizziamo dunque lo script del parser visto sopra. E’ molto più manutenibile e testabile se separiamo la logica di parsing in file distinti.
parser.js
// Costruttore var Parser = function() { }; // Parsing del testo Parser.prototype.parse = function(text) { var results = {}; // Suddividiamo i file in più linee var lines = text.split('\n'); lines.forEach(function(line) { var parts = line.split(' '); var letter = parts[1]; var count = parseInt(parts[2]); if(!results[letter]) { results[letter] = 0; } results[letter] += parseInt(count); }); return results; }; // Esportiamo il costruttore del parser da questo modulo. module.exports = Parser; |
Quel che è stato fatto è creare un nuovo file che contenga la logica del Parser.
Ci sono più modi per incapsulare il codice, nell’esempio è stato definito un nuovo oggetto Javascript perchè più facile da testare.
La parte chiave di questo pezzo di codice è la linea module.exports = Parser
. Che dice a Node cosa si sta esportando dal file corrente. In questo esempio si sta esportando il Costruttore, in modo che gli utenti possano creare delle istanze dell’oggetto Parser. E’ possibile esportare qualsiasi cosa.
Ora vediamo come importare questo file e usare il nuovo oggetto Parser:
my_parser.js
// includiamo il file parser.js var Parser = require('./parser'); // Carichiamo il modulo fs (filesystem). var fs = require('fs'); // Leggiamo in memoria il contenuto del file. fs.readFile('example_log.txt', function (err, logData) { // Se c'è un errore lo mostriamo // display the exception and kill our app. if (err) throw err; // logData è un Buffer, lo convertiamo in stringa. var text = logData.toString(); // Creiamo un'istanza dell'oggetto Parser var parser = new Parser(); // Chiamiamo il metodo parse. console.log(parser.parse(text)); // { A: 2, B: 14, C: 6 } }); |
I file sono inclusi esattamente come i moduli, con l’unica differenza che si indica il percorso anzichè soltanto il nome.
L’estensione .js è implicita, quindi volendo si può anche non mettere.
Dal momento che abbiamo esportato il costruttore, esso sarà ciò che verrà restituito dall’istruzione require.
Riepilogo
Node.js è una tecnologia estremamente potente e flessibile che può risolvere tutta una serie di problemi.
L’unico limite è l’immaginazione, le librerie principali sono pensate per fornire tasselli di puzzle utili nel costruire il quadro finale.
Per qualsiasi dubbio o informazione, commentate pure sotto.
Commenti