Accesso a database con NodeJs
Le applicazioni che si basano su Web Services diventano realmente utili ed efficaci solo quando diventa possibile aggiungere la possibilità di salvare i dati su un sistema software che gestisce la persistenza dell’informazione, ovvero un database.
Prima di procedere, studiare questa lezione.
CRUD
In un database engine vengono messe a disposizione, per ogni entità, un insieme di funzioni per l’accesso in lettura e scrittura dei dati, ed eventualmente modalità accesso in cui i dati vengono raggruppati insieme in strutture composte. In questa lezione faremo riferimento ad un database SQL (nello specifico Mysql) per cui sono previste queste quattro operazioni fondamentali:
– CREATE: aggiunta di un nuovo record per l’entità;
– READ: lettura di informazioni anche tramite una operazione che in SQL viene chiamata SELECT
– UPDATE: modifica di informazioni;
– DELETE: eliminazione di informazioni.
che insieme formano l’acronimo CRUD.
NodeJs e i database
Tramite NodeJs è possibile interfacciarsi a tutte le tecnologie sopra indicate ed è quindi uno strumento flessibile ed adattabile in base al contesto. Spesso tuttavia si utilizza in accoppiata con Express e MongoDB, principalmente per il fatto che si usa lo stesso formato per memorizzare i dati (JSON). Questo tipo di stack viene chiamato ME*N (Mongo Express e Node) dove la * indica il framework di frontend (MEAN se si usa Angular, MERN se si usa React).
Mongo tuttavia è un database relativamente meno diffuso rispetto alle tecnologie SQL, quindi in questa lezione andremo ad approfondire come creare una connessione con Mysql, un database molto diffuso nel mondo web.
Per farlo ci connetteremo ad un database esistente. [1]
E’ necessario è avere a disposizione l’accesso ad un db raggiungibile, ad esempio sfruttando servizi gratuiti su Internet[2].
Il servizio che qui andiamo ad approfondire è https://aiven.io che offre ad oggi un database Mysql gratuito da 5GB, utile per scopi didattici.
Dopo essersi registrati al servizio, viene fornita una pagina dashboard con le credenziali di accesso, il nome del db, l’host e la porta da raggiungere. Infine viene dato un certificato per l’accesso tramite SSL (unica connessione ammessa).
Useremo questi dati per configurare la connessione con NodeJs.
Un esempio: il progetto TODO
Ripartiamo dal progetto TODO visto nella precedente lezione e in questa nuova evoluzione andiamo a creare un database Mysql che permetta di salvare le todo in modo permanente.
Per farlo creiamo un database (con la procedura sopra descritta o altro database Mysql) ed all’interno creiamo una tabella che chiamiamo TODO e che contiene 3 campi:
id primary key autoincrement
name varchar
completed boolean
Vediamo i passi necessari.
1) Creiamo un nuovo progetto.
Eseguiamo quindi
> npm init
Se il progetto è già esistente (quello visto nella lezione del Webservice) utilizziamo il progetto esistente integrando e sostituendo opportunamente. Installiamo la libreria Mysql di nodeJs, nella versione che supporta anche SSL.
> npm install mysql
2
2)
Creiamo un file conf.json, che conterrà le credenziali di accesso al db.
{
"host": "XX",
"user": "XX",
"password": "XX",
"database": "XX",
port: ZZZZ,
"ca": "ca.pem"
}
Dove il file “ca.pem” contiene il certificato SSL necessario per connettersi all’host dove viene servito il database engine. Si ricorda che non è necessario se si usa una istanza non SSL.
3) Creiamo un file server.js ed inseriamo le seguenti istruzioni:
const fs = require('fs');
const mysql = require('mysql2');
const conf = JSON.parse(fs.readFileSync('conf.json'));
const conf = JSON.parse(fs.readFileSync('conf.json'));
conf.ssl = {
ca: fs.readFileSync(__dirname + '/ca.pem')
}
const connection = mysql.createConnection(conf);
La configurazione conf viene letta da filesystem e passata alla funzione createConnection della libreria Mysql. A questo punto, una volta ottenuta la variabile connection, è possibile eseguire query SQL, di qualsiasi tipo.
Si ricorda che l’esecuzione di query è asincrona, quindi prepariamo una Promise.
4) Scriviamo la funzione che esegue una query:
const executeQuery = (sql) => {
return new Promise((resolve, reject) => {
connection.query(sql, function (err, result) {
if (err) {
console.error(err);
reject();
}
console.log('done');
resolve(result);
});
})
}
Questa funzione facade permette quindi di eseguire qualsiasi query e gestirne il risultato. La connection appena creata è un oggetto che dispone del metodo query che permette di inviare sotto forma di testo la query al database. La callback dovrà gestire la risposta e l’eventuale errore nello schema classico delle Promise.
5) La nostra prima queryPer prima cosa creiamo la tabella todo, con la clausola “if not exists”.
const createTable = () => {
return executeQuery(`
CREATE TABLE IF NOT EXISTS todo
( id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
completed BOOLEAN )
`);
}
Il sistema se non ci sono errori risponderà con un oggetto JSON contenente il risultato dell’esecuzione e informazioni di log.
6) Creiamo la insert.
const insert = (todo) => {
const template = `
INSERT INTO todo (name, completed) VALUES ('$NAME', '$COMPLETED')
`;
let sql = template.replace("$NAME", todo.name);
sql = sql.replace("$COMPLETED", todo.completed);
return executeQuery(sql);
}
Come si può vedere la funzione insert riceve come parametro una nuova todo, e crea la stringa SQL da mandare al database. Come si può notare questo processo ha molte analogie con la funzione render vista nel frontend. Come si può notare usiamo lo stesso meccanismo dei segnaposto già visto nella generazione di html. Si ricorda che l’id è AUTO_INCREMENT quindi sarà generato dal database.
7) La SELECT
const select = () => {
const sql = `
SELECT id, name, completed FROM todo
`;
return executeQuery(sql);
}
La SELECT restituisce un array di oggetti Javascript, senza necessità di ulteriori conversioni (ci pensa la libreria Mysql a convertire il Result set in un array di dizionari). Qui un esempio:
[
{
id: 1,
name: 'studiare TPSI',
completed: 0
},
{
id: 2,
name: 'ripassare Storia',
completed: 0
}
]
8) Update
const update = (todo) => {
let sql = `
UPDATE todo
SET completed=$COMPLETED
WHERE id=$ID
`;
sql = sql.replace("%ID", todo.id);
sql = sql.replace("%COMPLETED", todo.completed);
return executeQuery(sql);
}
9) Delete
const delete = (id) => {
const sql = `
DELETE FROM todo
WHERE id=$ID
`;
sql = sql.replace("%ID", id);
return executeQuery(sql);
}
10) Testiamo tutte le query:
await createTable();
await insert({name: "test " + new Date().getTime(), completed: false});
const todos = await select();
todos[0].completed = true;
await update(todos[0]);
await delete(todos[0].id);
});
In questo codice viene creata la tabella, poi viene inserita una todo di test, poi questa viene modificata mettendola in stato completata, ed infine viene cancellata.
Notare che tutte le attività sono sempre asincrone.
Esercizio
La modifica dell’applicazione TODO della precedente dispensa con il db Mysql è lasciata per esercizio. Vanno implementati tutti i web services.
[1] In questa guida non è prevista alcuna guida su SQL, che si da quindi per conosciuto.
[2] Una risorsa disponibile è questa: https://aiven.io dove è possibile registrare una utenza con un DB gratuito. Per l’accesso si consiglia poi di installare questo tool gratuito: https://dbeaver.io che consente di accedere a qualsiasi tipo di database.