Gli sviluppatori JavaScript possono trarre grande vantaggio da Neo4j. Infatti, si può utilizzare Neo4j da JavaScript non solo lato server, con Node.js, ma anche direttamente dal browser, da quando Neo4j e il driver per JavaScript supportano l'autenticazione. È quindi realmente possibile integrare Neo4j in una single-page application (SPA) direttamente senza fornire alcun backend aggiuntivo, ovviamente se non serve per altre ragioni.
Neo4j e Node.js
Per invocare Neo4j da Node.js è necessario installare il plugin con NPM o Bower. Con npm, il package manager ufficiale di Node, andiamo nella cartella dei sorgenti (dove abbiamo eseguito il comando npm init
) e installiamo il package:
npm install neo4j-driver
L'API è molto semplice, più di quella che abbiamo visto per C# e Java: si basa su uno stile di programmazione funzionale. Per illustrarla, il modo migliore è usare un esempio. Per prima cosa, creiamo il driver per la connessione:
var neo4j = require('neo4j-driver').v1;
console.info('Creazione del driver');
var driver = neo4j
.driver("bolt://localhost:7687", neo4j.auth.basic("neo4j", "html"));
driver.onCompleted = function () {
console.info('Connesso a Neo4j');
};
driver.onError = function (error) {
console.error('Errore in connessione al database!', error);
process.exit(1);
};
var sessionError = function(error) {
console.error(error);
driver.close();
process.exit(1);
};
In queste prime righe abbiamo innanzitutto referenziato il driver versione 1. Nella terza riga abbiamo creato la connessione al database (tramite la funzione driver
), indicando anche l'autenticazione tramite username e password. Successivamente impostiamo due callback: onCompleted
, che viene invocata dopo che è stata aperta la connessione al server. e onError
, che serve per intercettare errori di connessione, di autenticazione e così via. In questo caso abbiamo semplicemente loggato gli eventi. La funzione sessionError
ci servirà nel prossimo blocco di codice: serve per gestire eventuali errori nell'utilizzo del driver stesso, loggandoli nella console e chiudendo il driver con driver.close
. Chiudere il driver è importante per evitare che rimangano connessioni aperte e risorse allocate inutilmente, ovviamente non tanto in questo esempio quanto nel ciclo di vita di un tipico programma in Node.js: applicazione web o servizio REST.
var session = driver.session();
session
.run('MERGE (n:User {name: {name}}) \
ON CREATE SET n.created_at = TIMESTAMP() \
RETURN id(n) as id, n.created_at AS ts',
{name: 'test@html.it'})
.then(function(result) {
result.records.forEach(function (record) {
console.info('Verificato utente test con id %d; timestamp %d',
record.get('id'),
record.get('ts'));
});
session
.run('MATCH (u:User) RETURN id(u) AS id, u.name')
.then(function(users) {
users.records.forEach(function(user) {
console.info('Utente %s con id %d',
user.get('u.name'),
user.get('id'));
});
session.close();
driver.close();
})
.catch(sessionError);
;
})
.catch(sessionError);
In questo caso abbiamo creato una sessione ed effettuato due operazioni molto semplici. La prima è un MERGE
in cui abbiamo verificato l'esistenza di un certo utente. Notare come (sesta riga) vengano passati i parametri utilizzando un object literal. Il risultato della chiamata viene passato alla funzione passata nel metodo test
. Questa funzione, in questo caso, scrive sulla console una riga per ogni record, quindi effettua una query Cypher (MATCH
), senza parametri, il cui risultato viene nuovamente stampato in console. È importante notare che la funzione catch
(non si tratta della keyword JavaScript ma di una funzione definita dal driver) definisce quale callback deve essere invocata in caso di errore (nel nostro caso sessionError
che abbiamo definito prima). In ogni caso (errore o no), chiudiamo sempre il driver con driver.close
.
Transazioni
Nell'esempio precedente abbiamo utilizzato le transazioni implicite (ogni comando una transazione). Il driver permette di gestire anche esplicitamente le transazioni, con una sintassi molto simile. Vediamo un solo esempio molto semplice. Creiamo un file JavaScript e ripetiamo il preambolo che abbiamo visto nel primo blocco di codice, quindi inseriamo questo:
var session = driver.session();
session.writeTransaction(function (transaction) {
transaction.run('CREATE (n:User {name: {name}}) RETURN id(n) as id',
{name: 'test2@html.it'})
.subscribe({
onNext: function (record) {
console.info('Creato utente di test con id %d', record.get('id'));
},
onError: function(error) {
console.error(error);
}
});
transaction.run('MERGE (g:Group {name: {groupName}})',
{groupName: 'Testers'})
.subscribe({
onCompleted: function() {
console.info('...creato gruppo');
},
onError: function(error) {
console.error(error);
}
});
})
.then(function() {
session.close();
driver.close();
})
.catch(sessionError);
In questo caso abbiamo creato una transazione con due comandi: le due invocazioni del metodo transaction.run
. Ogni volta chiamiamo in cascata la funzione subscribe
che permette di impostare delle callback:
onNext
per il processamento dei record restituiti;onError
per ricevere notifiche in caso di errore;onCompleted
in ogni caso di completamento con successo.
Utilizzo client-side
Neo4j può essere invocato direttamente dal browser. Se è tutto chiaro quanto detto lato server, allora lato client ci sono ben poche difficoltà. La differenza principale è che ovviamente abbiano una pagina HTML come questa:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdn.rawgit.com/neo4j/neo4j-javascript-driver/1.4/lib/browser/neo4j-web.min.js"></script>
<script src="test_client.js"></script>
</head>
<body>
<div id="result"></div>
<div id="messages"></div>
</body>
</html>
Come dipendenza, per semplicità e brevità in questo caso usiamo jQuery e il driver JavaScript per Neo4j. Inoltre ci sono due div
contenenti rispettivamente i risultati delle query ed eventuali messaggi o errori.
Nel file test_client.js andiamo ad inserire il codice JavaScript che invoca il database. Prendiamo il primo esempio, e lo convertiamo per un utilizzo in pagina web. È sufficiente modificare la prima riga (invece di invocare require
invochiamo direttamente il driver) e modificare le funzioni di output (invece di console
utilizziamo jQuery per scrivere sui div
). Ovviamente avremmo potuto caricare i messaggi e i risultati su un array con cui fare il binding con Angular.JS o Vue.js o qualsiasi altro framework JavaScript. Inoltre, sempre per semplicità in questo caso username e password sono cablate nel codice. Avremmo potuto ovviamente prenderle da un form compilato dall'utente oppure dal sessionStorage
.
var neo4j = neo4j.v1;
var driver = neo4j.driver("bolt://localhost:7687", neo4j.auth.basic("neo4j", "html"));
driver.onCompleted = function () {
$("#messages").append('
Connesso a Neo4j...
');
};
driver.onError = function (error) {
$("#messages").append('
Errore in connessione al database!' + error + "
");
};
var sessionError = function(error) {
$("#messages").append('
Errore:' + error + "
");
driver.close();
};
var session = driver.session();
session
.run('MERGE (n:User {name: {name}}) ON CREATE SET n.created_at = TIMESTAMP() \
RETURN id(n) as id, n.created_at AS ts', {name: 'test@html.it'})
.then(function(result) {
result.records.forEach(function (record) {
$("#result").append('
Verificato utente test con id ' + record.get('id') + "; timestamp " + record.get('ts') + "
");
});
session
.run('MATCH (u:User) RETURN id(u) AS id, u.name')
.then(function(users) {
users.records.forEach(function(user) {
$("#result").append('
Utente ' + user.get('u.name') + ' con id ' + user.get('id') + "
");
});
session.close();
driver.close();
})
.catch(sessionError);
;
})
.catch(sessionError);