Persistenza dei Dati e Sessioni in React
() translation by (you can also view the original English article)



Avere a disposizione una funzione "remember me" è davvero molto utile, e l'implementazione con React e Express è relativamente facile. Continuando dall'ultima parte dove abbiamo impostato un'applicazione chat WebRTC, aggiungeremo ora sessioni persistenti con Mongo e un database di un elenco online di utenti.
Sessioni?
Se non avete mai usato sessioni finora, in breve sono molto simili ai cookies, nel senso che le sessioni vi consentono di tracciare gli utenti attivi dell'applicazione in tempo reale. Le sessioni lavorano attraverso un session cookie
che viene inoltrato nell'header request / response dall'applicazione.
Così cookies e sessioni sono intrecciate per natura. Quindi perché necessitiamo delle sessioni se abbiamo già i cookies? Ciò che vi danno in aggiunta le sessioni è l'abilità di definire il back-end usato dalla vostra applicazione. Significa che ogni volta che l'informazione viene richiesta dalla vostra applicazione può essere richiamata dal database.
Così in un esempio di vita reale per la nostra chat, ora possiamo memorizzare l'username dell'utente — e se abbiamo riconfigurato un po' l'applicazione, possiamo anche inserire l'intera cronologia della chat nel database per la registrazione.
In questo prossimo esempio useremo un database Mongo per l'archiviazione persistente lato back-end. Questa è una delle diverse opzioni per l'archiviazione della sessione, e memcache è un'altra che raccomandiamo altamente per la configurazione in produzione in larga scala .
Archiviazione dei documenti
Mongo è un motore di archiviazione di documenti NoSQL piuttosto che un archivio di dati relazionale come il popolare MySQL. È davvero facile prendere confidenza con NoSQL se provenite da MySQL o databases simili e avete bisogno di avvicinarvi velocemente a Mongo — non vi ci vorrà molto. Le differenze più importanti che dovreste conoscere sono le seguenti:
- Come suggerisce il nome, NoSQL non utilizza SQL per eseguire queries. Invece i dati vengono estratti con chiamate al metodo; ad esempio
db.collectionName.find()
sarebbeSELECT * FROM table
. - La terminologia è differente: in MySQL abbiamo Tabelle, Righe e Colonne, mentre in Mongo sono presenti Collezioni, Documenti e Chiavi.
- I dati sono strutturati nello stesso modo in cui è strutturato un oggetto JSON.
Se non avete ancora Mongo, installatelo attraverso il vostro package manager. Nelle distribuzioni Linux per esempio:
1 |
$ sudo apt-get install mongodb |
Una volta installato Mongo possiamo semplicemente aggiungere il supporto di Mongo alla nostra applicazione con il modulo mongoose
disponibile attraverso npm. Installate mongoose
nel modo seguente:
1 |
$ npm install mongoose --save |
Aggiungiamo ora Mongo alla nostra applicazione. Aprite il vostro editor, aprite app.js
e impostate la parte alta del vostro script nel modo seguente.
1 |
//Configure our Services
|
2 |
var PeerServer = require('peer').PeerServer, |
3 |
express = require('express'), |
4 |
mongoose = require('mongoose'), |
5 |
assert = require('assert'), |
6 |
events = require('./src/events.js'), |
7 |
app = express(), |
8 |
port = process.env.PORT || 3001; |
9 |
|
10 |
//Connect to the database
|
11 |
mongoose.connect('mongodb://localhost:27017/chat'); |
12 |
var db = mongoose.connection; |
13 |
|
14 |
mongoose.set('debug', true); |
15 |
|
16 |
db.on('error', console.error.bind(console, '# Mongo DB: connection error:')); |
Includiamo mongoose
con require('mongoose')
e utilizziamo quindi la nostra connessione al database attraverso mongoose.connect('mongodb://localhost:27017/chat');
/chat
definisce il nome del database a cui ci stiamo connettendo.
Poi, a fini di sviluppo, raccomandiamo di impostare il debugging su on.
1 |
mongoose.set('debug', true); |
Infine aggiungiamo un handler per gli eventi d'errore:
1 |
db.on('error', console.error.bind(console, '# Mongo DB: connection error:')); |
Quindi potete aggiungere il controllo per la connessione con il codice seguente:
1 |
db.once('open', function (callback) { |
2 |
console.log("# Mongo DB: Connected to server"); |
3 |
}
|
mongoose
viene usato in modo che una volta che l'istanza db
riceve l'evento open
, inizieremo l'esecuzione della nostra connessione mongo. Avremo così bisogno di inserire il nostro codice esistente in questa nuova connessione mongo in modo da utilizzarla.
Ecco un elenco di codice intero con l'aggiunta di mongoose e l'inserimento di righe e loro cancellazione quando gli utenti sono online e offline.
1 |
|
2 |
//Configure our Services
|
3 |
var PeerServer = require('peer').PeerServer, |
4 |
express = require('express'), |
5 |
mongoose = require('mongoose'), |
6 |
assert = require('assert'), |
7 |
events = require('./src/events.js'), |
8 |
app = express(), |
9 |
port = process.env.PORT || 3001; |
10 |
|
11 |
//Tell express to use the 'src' directory
|
12 |
app.use(express.static(__dirname + '/src')); |
13 |
|
14 |
//Connect to the database
|
15 |
mongoose.connect('mongodb://localhost:27017/chat'); |
16 |
var db = mongoose.connection; |
17 |
|
18 |
mongoose.set('debug', true); |
19 |
|
20 |
db.on('error', console.error.bind(console, '# Mongo DB: connection error:')); |
21 |
|
22 |
db.once('open', function (callback) { |
23 |
|
24 |
console.log("# Mongo DB: Connected to server"); |
25 |
|
26 |
//Setup our User Schema
|
27 |
var usersSchema = mongoose.Schema({username: String}); |
28 |
var User = mongoose.model('User', usersSchema); |
29 |
|
30 |
//Configure the http server and PeerJS Server
|
31 |
var expressServer = app.listen(port); |
32 |
var io = require('socket.io').listen(expressServer); |
33 |
var peer = new PeerServer({ port: 9000, path: '/chat' }); |
34 |
|
35 |
//Print some console output
|
36 |
console.log('#### -- Server Running -- ####'); |
37 |
console.log('# Express: Listening on port', port); |
38 |
|
39 |
peer.on('connection', function (id) { |
40 |
io.emit(events.CONNECT, id); |
41 |
console.log('# Connected: ', id); |
42 |
|
43 |
//Store Peer in database
|
44 |
var user = new User({ username: id }); |
45 |
user.save(function (err, user) { |
46 |
if (err) return console.error(err); |
47 |
console.log('# User '+ id + ' saved to database'); |
48 |
});
|
49 |
|
50 |
});
|
51 |
|
52 |
peer.on('disconnect', function (id) { |
53 |
io.emit(events.DISCONNECT, id); |
54 |
console.log('# Disconnected: ', id); |
55 |
|
56 |
//Remove Peer from database
|
57 |
User.remove({username: id}, function(err){ if(err) return console.error(err)}); |
58 |
|
59 |
});
|
60 |
|
61 |
});
|
Per vederlo funzionante avviamo l'applicazione chat. Eseguiamo npm start
.
Ora connettiamoci alla chat nel browser come di consueto (default http://localhost:3001).
Una volta connessi alla chat in una nuova finestra di terminale eseguite mongo chat
per aprire la cli di mongo.
1 |
$ mongo chat
|
2 |
MongoDB shell version: 2.0.6 |
3 |
connecting to: chat |
4 |
> db.users.find() |
5 |
{ "username" : "CameronLovesPigs", "_id" : ObjectId("5636e9d7bd4533d610040730"), "__v" : 0 } |
Qui avete il registro dei documenti archiviati in mongo
, e ora potete sempre controllare quanti utenti sono online eseguendo nel prompt mongo db.users.count()
.
1 |
> db.users.count() |
2 |
3 |
Aggiungere Sessioni all'Applicazione
Dal momento che abbiamo usato Express per sviluppare l'applicazione, questa parte sarà davvero molto facile e richiederà l'installazione di un paio di moduli da npm
per permetterci di proseguire.
Prendete i pacchetti express-session
e connect-mongo
da npm:
1 |
$ npm install express-session connect-mongo cookie-parser --save |
Ora includeteli all'inizio di app.js
:
1 |
var PeerServer = require('peer').PeerServer, |
2 |
cookieParser = require('cookie-parser'), |
3 |
express = require('express'), |
4 |
session = require('express-session'), |
5 |
mongoose = require('mongoose'), |
6 |
MongoStore = require('connect-mongo')(session), |
7 |
//...
|
Dopo che avete impostato mongoose.connect
potete configurare le sessioni con express. Cambiate il vostro codice nel modo seguente; potete specificare la vostra stringa secret
.
1 |
//Connect to the database
|
2 |
mongoose.connect('mongodb://localhost:27017/chat'); |
3 |
var db = mongoose.connection; |
4 |
|
5 |
mongoose.set('debug', true); |
6 |
|
7 |
db.on('error', console.error.bind(console, '# Mongo DB: connection error:')); |
8 |
|
9 |
app.use(cookieParser()); |
10 |
app.use(session({ |
11 |
secret: 'supersecretstring12345!', |
12 |
saveUninitialized: true, |
13 |
resave: true, |
14 |
store: new MongoStore({ mongooseConnection: db }) |
15 |
}))
|
Qui un'impostazione cruciale da notare è saveUninitialized: true
dentro l'ultimo app.use
. Assicurerà che le sessioni vengano salvate.
Specifichiamo dove grazie alla proprietà store
, che impostiamo nell'istanza MongoStore
indicando quale connessione usare attraverso mongooseConnection
e il nostro oggetto db
.
Per archiviare la sessione abbiamo bisogno di usare express per la gestione della richiesta perché abbiamo bisogno di accedere al valore della richiesta, ad esempio:
1 |
//Start persistent session for user
|
2 |
app.use(function(req, res, next) { |
3 |
req.session.username = id; |
4 |
req.session.save(); |
Ciò creerà la variabile req.session.username
con il valore inserito dall'utente e lo salveremo per utilizzo successivo.
Poi potremo cercare questo valore con il codice lato client ed effettuare il log in automatico dell'utente quando aggiorna, cosicché non venga mai escluso dalla chat ed esegua automaticamente il login con l'username scelto.
Interessante anche notare che, dal momento che abbiamo delle sessioni supportate da database, nel caso che gli sviluppatori cambino l'applicazione e il back-end venga ricaricato, gli utenti che hanno già effettuato il login rimarranno loggati essendo ora la sessione d'archiviazione persistente. Questa è una grande funzionalità per tenere i vostri utenti contenti e loggati mentre state sviluppando, o se è presente una disconnessione da un client non stabile.
Login persistente
Ora che abbiamo la sessione cookie impostata, lavoriamo sull'aggiungere il login persistente al nostro codice front-end.
Finora abbiamo solo usato il route di default fornito da Express per un'applicazione SPA, e non abbiamo definito alcuna gestione routing per Express. Come menzionato prima, per avere accesso alla sessione avrete bisogno delle variabili di richiesta / risposta di Express.
Prima di tutto abbiamo bisogno di una route in modo da accedere all'oggetto request
fornito da Express e visualizzarlo per il debug. All'interno del file di configurazione Express /app.js
, aggiungete le righe seguenti all'inizio del file, dopo il setup della sessione:
1 |
app.use(session({ |
2 |
secret: 'supersecretstring12345!', |
3 |
saveUninitialized: true, |
4 |
resave: true, |
5 |
store: new MongoStore({ mongooseConnection: db }) |
6 |
}))
|
7 |
|
8 |
app.get('/', function (req, res) { |
9 |
res.sendFile(__dirname +'/src/index.html'); |
10 |
if(req.session.username == undefined){ |
11 |
console.log("# Username not set in session yet"); |
12 |
} else { |
13 |
console.log("# Username from session: "+ req.session.username); |
14 |
|
15 |
}
|
16 |
});
|
Ora abbiamo qualche registrazione di base per vedere cosa sta succedendo con il nostro valore di sessione. Per impostarlo, abbiamo bisogno di configurare le routes get e set nel modo seguente:
1 |
//Save the username when the user posts the set username form
|
2 |
app.post('/username', function(req, res){ |
3 |
console.log("# Username set to "+ req.body.username); |
4 |
req.session.username = req.body.username; |
5 |
req.session.save(); |
6 |
console.log("# Session value set "+ req.session.username); |
7 |
res.end(); |
8 |
});
|
9 |
|
10 |
//Return the session value when the client checks
|
11 |
app.get('/username', function(req,res){ |
12 |
console.log("# Client Username check "+ req.session.username); |
13 |
res.json({username: req.session.username}) |
14 |
});
|
Queste due routes funzioneranno come il get e il set per la variabile di sessione username. Ora con un po' di semplice JavaScript possiamo implementare l'autologin per la nostra applicazione. Aprite src/App.js
e modificatelo nel modo seguente:
1 |
/* global EventEmitter, events, io, Peer */
|
2 |
/** @jsx React.DOM */
|
3 |
|
4 |
$(function () { |
5 |
'use strict'; |
6 |
|
7 |
// Check for session value
|
8 |
$(document).ready(function(){ |
9 |
$.ajax({ |
10 |
url: '/username' |
11 |
}).done(function (data) { |
12 |
console.log("data loaded: " + data.username); |
13 |
if(data.username) |
14 |
initChat($('#container')[0], data.username); |
15 |
});
|
16 |
});
|
17 |
|
18 |
// Set the session
|
19 |
$('#connect-btn').click(function(){ |
20 |
var data = JSON.stringify({username: $('#username-input').val()}); |
21 |
$.ajax({ url: '/username', |
22 |
method: "POST", |
23 |
data: data, |
24 |
contentType: 'application/json', |
25 |
dataType: 'json' |
26 |
});
|
27 |
});
|
28 |
|
29 |
// Initialize the chat
|
30 |
$('#connect-btn').click(function () { |
31 |
initChat($('#container')[0], |
32 |
$('#username-input').val()); |
33 |
});
|
34 |
|
35 |
function initChat(container, username) { |
36 |
var proxy = new ChatServer(); |
37 |
React.renderComponent(<ChatBox chatProxy={proxy} |
38 |
username={username}></ChatBox>, container); |
39 |
}
|
40 |
|
41 |
window.onbeforeunload = function () { |
42 |
return 'Are you sure you want to leave the chat?'; |
43 |
};
|
44 |
|
45 |
});
|
Con il servizio $.ajax
di jQuery creiamo una richiesta per controllare il valore della variabile di sessione quando document
diventa disponibile. Se è impostato inizializziamo quindi il nostro componente React con il valore archiviato, risultante in una funzionalità autologin per gli utenti.
Avviate nuovamente la chat con npm start
e guardate nel browser le sessioni funzionanti.
Conclusioni
Ora avete visto quanto è facile usare Mongoose insieme a Express e impostare le sessioni Express. Approfondendo ulteriormente lo sviluppo dell'applicazione, con React come view controller collegato agli elementi del database creerete applicazioni interessanti.
Se volete procedere ulteriormente con React e guardare come i componenti possono comunicare tra l'uno e l'altro internamente attraverso il framework, questa guida dalla documentazione ufficiale vi risulterà molto utile.