In alcuni articoli precedenti, pubblicati sulle nostre pagine, abbiamo proposto un’introduzione a Node.js, ambiente Javascript per l’esecuzione di applicazioni stand-alone da eseguire senza il supporto di un browser. Abbiamo visto come integrare Express.js in Node.js fornisca un server HTTP già pronto per configurare le nostre API REST. Oggi proseguiremo il discorso approfondendo alcuni aspetti di Express.js ancora non affrontati e forniremo il backend di un database relazionale.
Node.js e Sqlite
Per questo esempio, abbiamo scelto di dotare il nostro servizio di un database SQLite, ma buona parte dei principi che enunceremo varranno per un pò tutti i DBMS relazionali esistenti. Sqlite – per chi non lo conoscesse – è uno dei motori di database più diffusi al mondo. Ciò che lo distingue è la sua leggerezza ed estrema portabilità. Infatti Sqlite non è un servizio da mandare in esecuzione ma una libreria che va collegata alla propria applicazione – lo si può fare praticamente con ogni tecnologia – ed è in grado di mantenere un database relazionale in un unico file.
Per il nostro esempio, abbiamo preparato un semplice database – di nome dbesempio.db – composto da un’unica tabella, la cui struttura è mostrata nel seguente script SQL:
CREATE TABLE `persone` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `nome` TEXT, `cognome` TEXT, `eta` INTEGER, `citta` TEXT );
Ogni suo record rappresenta una persona che abbiamo registrato, definita da nome , cognome, età e città, il tutto univocamente identificato da una chiave primaria intera auto-incrementante.
In Node.js dovremo, per prima cosa, integrare il modulo per Sqlite, ma grazie a npm non sarà affatto complicato.
Impartiremo da riga di comando:
npm install sqlite3 --save
Al termine dello scaricamento, avremo tale modulo a disposizione della nostra installazione Node.js e per attivarlo saranno sufficienti le seguenti righe nel nostro codice Javascript:
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database('dbesempio.db');
A questo punto, l’oggetto db riceverà comandi da tradurre in operazioni sul database dbesempio.db.
Il modulo per Sqlite offre un insieme di metodi per svolgere tutte le operazioni principali. Nell’esempio, eseguiremo query ed inserimento di dati. Il funzionamento della libreria è asincrono: il metodo che utilizzeremo per i comandi richiederà il codice SQL ed una funzione di callback che tratterà i risultati ottenuti. Come si può presumere, a seconda del comando inoltrato, la funzione di callback avrà funzionalità e scopi diversi.
L’esempio
Supponiamo che il nostro server sia messo in esecuzione all’indirizzo http://192.168.100.1 alla porta TCP 4157.
Nelle nostre API predisporremo tre possibili richieste:
- una GET sull’URL http://192.168.100.1/db/tutti che restituirà un array di oggetti JSON ognuno contenente i dati di una persona;
- una GET sull’URL http://192.168.100.1/db/:citta dove al posto del parametro :citta passeremo direttamente il nome della città da usare per filtrare i risultati: otterremo un array JSON anche in questo caso, ma contenente solo i dati di persone residenti nella città indicata. Ad esempio:
http://192.168.100.1/db/Roma
restituità solo i record che vedranno nel campo citta la parola “Roma”;
- una POST all’indirizzo http://192.168.100.1/db/nuova che riceverà nel corpo della richiesta un oggetto JSON con i nuovi dati. Il risultato sarà, in caso di successo, l’id del record appena creato ed il codice di stato HTTP sarà impostato a 202, viceversa, qualora si verificassero errori, il codice sarà 500 ed il corpo della risposta conterrà l’oggetto di errore restituito da Express.
Questo il codice:
var express = require('express');
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database('dbesempio.db');
var bodyParser=require('body-parser');
var app=express();
app.use(bodyParser.json());
app.get('/db/tutti', function (req, res) {
db.all("SELECT * FROM persone",
function(e,r)
{
res.json(r);
});
});
app.get('/db/:citta', function(req,res)
{
db.all("SELECT * FROM persone WHERE citta=?",req.params.citta,
function(e,r)
{
res.json(r);
});
});
app.post('/db/nuova', function(req,res)
{
db.run("INERT INTO persone (nome,cognome,citta,eta) VALUES (?,?,?,?)", [req.body.nome,req.body.cognome,req.body.citta,req.body.eta],
function(e,r)
{
if (e==null)
res.status(202).json({"id":this.lastID});
else
res.status(500).end();
});
});
app.listen(4157, function () {
console.log('Server in esecuzione sulla porta 4157...');
});
Per eseguirlo, dovremo includere nella cartella che lo contiene il database dbesempio.db ed attivare lo script che nel nostro caso si chiama app.js:
node app.js
Come vediamo nelle ultime righe del listato, il servizio verrà avviato sulla porta 4157 ed in output verrà prodotto il messaggio:
Server in esecuzione sulla porta 4157...
Per svolgere le prove sarà necessario un client HTTP. Se ne può usare uno di proprio gradimento o creare un programma che interagisca col servizio: a parere nostro, a scopo di test, uno strumento molto valido può riultare Postman, di cui abbiamo parlato in un precedente articolo.
Analizziamo il codice
Vediamo ora nel dettaglio come abbiamo realizzato il nostro servizio. Dopo le inizializzazioni delle prime righe, istanziamo un body parser:
var bodyParser=require('body-parser');
...
...
app.use(bodyParser.json());
che si farà carico di interpretare in automatico i corpi delle richieste compilati in formato JSON.
Come fatto nelle lezioni precedenti, configureremo le funzionalità delle API tramite gli appositi metodi get e post (eventualmente ci servissero, esistono anche delete e put) contenuti nell’oggetto app e, al loro interno, invocheremo le operazioni relative al database.
Il metodo all del modulo Sqlite viene usato per recuperare tutti i risultati di una query. Nel metodo di callback, verranno restituiti due oggetti contenenti, rispettivamente, un eventuale errore o il set di risultati ottenuti:
db.all("SELECT * FROM persone",
function(e,r)
{
res.json(r);
});
Essendo i risultati restituiti come array di oggetti Javascript, potremo direttamente inviarli al client con il metodo json.
La seconda funzionalità GET deve eseguire una query in base al parametro “:citta” inviato all’interno dell’URL:
db.all("SELECT * FROM persone WHERE citta=?",req.params.citta,
function(e,r)
{
res.json(r);
});
Notare in questo caso come la query sia stata costituita in forma parametrica: in pratica il codice SQL contiene solo la parte immutabile mentre i valori della clausola WHERE sono sostituiti da punti interrogativi (?). Questi ultimi funzioneranno a mo’ di segnaposto e, in fase di esecuzione, verranno rimpiazzati da altrettanti valori passati prima della funzione di callback. Questa modalità evita noiose concatenazioni di SQL preimpostato e valori passati a tempo di esecuzione.
Anche nel metodo post il comando SQL è espresso in forma parametrica ed i valori attuali saranno passati sotto forma di array essendo più di uno.
db.run("INSERT INTO persone (nome,cognome,citta,eta) VALUES (?,?,?,?)", [req.body.nome,req.body.cognome,req.body.citta,req.body.eta],
function(e,r)
{
if (e==null)
res.status(202).json({"id":this.lastID});
else
res.status(500).end();
});
Nella funzione di callback il parametro “e” sarà null a meno che non si siano verificati errori mentre l’id dell’ultimo inserimento sarà reso disponibile nella proprietà lastID disponibile tramite il riferimento this.
Il metodo status applicato alla risposta permette di indicare il codice di stato HTTP.
Conclusioni
Con questo articolo abbiamo fatto un ulteriore passo avanti nel nostro percorso: abbiamo introdotto un database relazionale che potrà così essere gestito da remoto, ad esempio, inviando comandi da una nostra applicazione Android. In aggiunta a quanto visto si potranno usare ulteriori metodi del modulo Sqlite come each per eseguire la funzione di callback su ogni record restituito da una query oppure get che elabora solo il primo record dei risultati (pensiamo ad una SELECT COUNT che deve solo fornire una riga con il numero di record contenuti nella tabella). Ci sono anche prepared statement e altre funzioni illustrate nella documentazione ufficiale.
Quanto fatto sinora sarà proseguito nelle prossime lezioni dove vedremo come arricchire ulteriormente le nostre API REST con Node.js: continuate quindi a seguirci e suggerite idee attraverso i commenti!











No Responses to “Node.js: API REST con database relazionale”