Tutoriel Vidéo Socket.io Tchat Socket.IO : Persistance via MySQL

Télécharger la vidéo Télécharger les sources

Je vous propose aujourd'hui de continuer un ancien tutoriel sur la création d'un tchat avec NodeJS et Socket.IO pour y ajouter plusieurs fonctionnalités.

  • Permettre aux gens d'utiliser leur compte sur le site PHP pour se connecter au tchat NodeJS
  • Sauvegarder les messages via une base de données pour persister les informations

Relier à un site existant

Le tchat et le site fonctionnent comme 2 entités distinctes et on ne peut pas récupérer simplement les informations de notre utilisateur depuis l'application NodeJS (nous n'avons pas accès aux informations stockées dans la Session PHP ou autre). La solution est d'utiliser la base de données comme élément de liaison :

  • On génère un token unique pour chaque utilisateur côté PHP et on le stock en base de données.
  • On transmet ce token, ainsi que l'id utilisateur, au serveur NodeJS via les sockets.
  • Le serveur NodeJS vérifie les informations en se connectant à la base de données.

Côté PHP il suffit d'envoyer les informations au javascript :

<script>
startTchat = function(user_id, token){
    socket.emit('login', {
        id: user_id,
        token: token
    });
}
startTchat(<?= $user->id; ?>, "<?= $user->token ?>");
</script>

Ces informations sont alors récupérées et vérifiées côté NodeJS

var mysql = require('mysql');
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: 'root',
    database: 'demo'
});
connection.connect(function(err){
    if(err){
        console.error('Impossible de se connecter ', err);
    }
});

var io = require('socket.io').listen(httpServer);
var users = {};

io.sockets.on('connection', function(socket){

    var me = false;

    socket.on('login', function(user){
        connection.query('SELECT * FROM users WHERE id = ?', [user.id], function (err, rows, fields) {
            if(err){
                socket.emit('error', err.code);
            }else if(rows.length === 1 && rows[0].token === user.token) {
                me = {
                    username: rows[0].username,
                    id: rows[0].id,
                    avatar: "https://gravatar.com/avatar/" + crypto.createHash('md5').update(rows[0].email).digest('hext') + '?s=50'
                };
                socket.emit('logged');
                users[me.id] = me;
                io.sockets.emit('newusr', me);
                getLastComments()
            } else {
                io.sockets.emit('error', 'Aucun utilisateur ne correspond');
            }
        })
    });

Persister les messages

L'autre point que l'on souhaitait mettre en place était la persistance des messages. Pour cela nous allons stocker les messages au sein de notre base de données. On utilisera MySQL ici mais une base de données Redis serait plus adaptée vu la quantité d'écriture / lecture que l'on va avoir :

socket.on('newmsg', function(message){
    if(message.message === '' || me.id === undefined) {
        socket.emit('error', 'Vous ne pouvez pas envoyer un message vide')
    } else if (me.id === undefined){
        socket.emit('error', 'Vous devez être identifié pour envoyer un message')
    } else {
        message.user = me;
        message.created_at = Date.now();
        connection.query('INSERT INTO messages SET user_id = ?, message = ?, created_at = ?', [
            message.user.id,
            message.message,
            new Date(message.created_at)
        ], function (err) {
            if(err){
                socket.emit('error', err.code)
            } else {
                io.sockets.emit('newmsg', message)
            }
        })
    }
});

Lorsqu'un nouvel utilisateur arrive sur le tchat on peut alors récupérer les commentaires depuis la base de données. J'extrais ce comportement dans une fonction pour plus de clarté.

var getLastComments = function(){
    connection.query('' +
        'SELECT users.id as user_id, users.username, users.email, messages.message, UNIX_TIMESTAMP(messages.created_at) as created_at ' +
        'FROM messages ' +
        'LEFT JOIN users ON users.id = messages.user_id ' +
        'ORDER BY messages.created_at DESC ' +
        'LIMIT 10', function(err, rows){
        if(err){
            socket.emit('error', err.code);
        } else {
            var messages = [];
            rows.reverse(); // On veut les plus vieux en premier
            for(k in rows){
                var row = rows[k];
                var message = {
                    message: row.message,
                    created_at: row.created_at * 1000,
                    user: {
                        id: row.user_id,
                        username: row.username,
                        avatar: "https://gravatar.com/avatar/" + crypto.createHash('md5').update(row.email).digest('hex') + '?s=50'
                    }
                };
                messages.push(message)
            }
            socket.emit('newmsg', messages)
        }
    })
};

Conclusion

Même si au premier abord avoir 2 applications qui fonctionnent en parallèle peut s'avérer problématique cela nous apporte aussi plus de flexibilité. Si demain on souhait changer la base de données utilisées par le tchat on peut le faire sans avoir à changer quoi que ce soit sur la partie client. Les applications sont indépendante l'une de l'autre.