Laravel 4 and NodeJs/Redis pub/sub realtime notifications

Currently I am building an application where we can fill in live scores and I needed something to update all my visitors whenever a score has been updated by one of the admins.

Whenever an admin updates the score via the Laravel 4 backend I fire an event and publish it to Redis. I’ve setup a simple NodeJS server which listens to Redis for incoming changes. NodeJS will redirect the message to all Socket.IO clients.

Laravel

1. Open config//database.php
2. Configure:

'redis' => array(

		'cluster' => false, //publish won't work when set to true

		'default' => array(
			'host'     => '127.0.0.1',
			'port'     => 6379,
			'database' => 0,
		),

	),

3. Create app\events folder and update composer.json

"autoload": {
	"classmap": [
	    ....
            "app/events",

4. Create UpdateScoreEventHandler.php in the events folder:

class UpdateScoreEventHandler {

    CONST EVENT = 'score.update';
    CONST CHANNEL = 'score.update';

    public function handle($data)
    {
        $redis = Redis::connection();
        $redis->publish(self::CHANNEL, $data);
    }
}

5.Create listeners.php (next to routes.php/filters.php):

Event::listen(UpdateScoreEventHandler::EVENT, 'UpdateScoreEventHandler');

6. Add to start/global.php:

require app_path().'/listeners.php';

7. Fire event in controller (update score) :

Event::fire(UpdateScoreEventHandler::EVENT, array($updatedMatch));

8. Run composer update (class loading)

NodeJS

1. Download NodeJS via the website (http://nodejs.org/)
2. Create a nodejs folder in Laravel's root.
3. /nodejs/npm install socket.io express redis
4. Create nodejs server (e.g. server.js) and store it /nodejs

var express =   require('express'),
    http =      require('http'),
    server =    http.createServer(app);

var app = express();

const redis =   require('redis');
const io =      require('socket.io');
const client =  redis.createClient();

server.listen(3000, 'localhost');

io.listen(server).on('connection', function(client) {
    const redisClient = redis.createClient()
    redisClient.subscribe('score.update');

    redisClient.on("message", function(channel, message) {
        //Channel is e.g 'score.update'
        client.emit(channel, message);
    });

    client.on('disconnect', function() {
        redisClient.quit();
    });
});

Client

1. Include Socket.io client in your page (https://github.com/LearnBoost/socket.io/releases)
2. Add code below to receive messages when a score has been updated.

<script type="text/javascript">// <![CDATA[
            var socket = io.connect('http://127.0.0.1:3000/');

            //socket.on('connect', function(data){
            //    socket.emit('subscribe', {channel:'score.update'});
            //});

            socket.on('score.update', function (data) {
                //Do something with data
                console.log('Score updated: ', data);
            });

// ]]></script>

5. Start the server

/nodejs/node server.js

Redis

1. Download Redis (http://redis.io/download)
2. Go to extacted folder
3. Go to src folder
4. Start the server

./redis-server

Optional – Monitor Redis (requests)

1. ./redis-cli monitor

27 comments to Laravel 4 and NodeJs/Redis pub/sub realtime notifications

  • MarkL

    Good tutorial. You should have gone with SockJS, which is bounds better than SocketIO.

  • Why is it better?

    Will look into it. Thanks for your comment!

  • will

    I am unable to get your example to work. it seems that socket.io is not serving the client js? I managed to find a cdn hosting it but now my client cannot connect to the socket server

  • When you start the server do you see ‘info – socket.io started’ in your console?

    Did you download and added the socket.io javascript file to your page?

  • Robert Klep

    Instead of creating a separate Redis client for each incoming socket.io connection, you can create just one and use io.sockets.emit() to broadcast the message to all clients.

  • Hi Robert,

    This works as well but not with io.sockets.emit(). Is this what you mean?

    var express =   require('express'),
        http =      require('http'),
        server =    http.createServer(app);
    
    var app = express();
    
    const redis =   require('redis');
    const io =      require('socket.io');
    const redisClient = redis.createClient()
    
    redisClient.subscribe('score.update');
    
    server.listen(1337);
    
    io.listen(server)
        .set('log level', 1) //1 = warn
        .on('connection', function(client) {
    
                redisClient.on("message", function(channel, message) {
                    //Channel is e.g 'score.update'
                    client.emit(channel, message);
                });
    
                client.on('disconnect', function() {
                    redisClient.quit();
                });
            }
        );
    
  • Robert Klep

    I was thinking more like this:

    const io = require('socket.io').listen(1338);
    const redis = require('redis');
    const redisClient = redis.createClient();

    redisClient.subscribe('score.update');

    redisClient.on('message', function(channel, message) {
    io.sockets.emit(channel, message);
    });

  • It does work! Thanks. Is your approach better?

  • Robert Klep

    It just leaves out some code that isn’t strictly necessary :-)

  • themarko

    This is great! Never used nodejs nor redis. Does anyone know any basic resources to learn these?

  • Hi,

    Very useful information for pushing notification on real time with redis and socket.io.
    But how it is going to work if we use Redis Store instead of memory store for the server side code, i m stuck in that.

  • Why use Redis at all in this case? Why not just let node.js receive published events directly via HTTP?

  • Colin,

    If you have multiple node instances running, you’ll need a common ground such as redis in order for everything to be in sync.

    Mark

  • Mark, there is going to be no benefit to having multiple node instances running when Redis itself is single-threaded. So unless you also *need* multiple Redis instances, I maintain that Redis is mostly a useless additional point of failure in this particular use-case. Even then, you could quite easily have node instances which subscribe to events from the master node server and re-publish all received events. I won’t bother to test, but I’d bet that a node-only cluster would scale just as well as a node+Redis cluster and with less maintenance.

    I suppose it comes down to if you prefer Redis as your protocol for publishing events versus HTTP..

  • Colin, if you have multiple *node* instances, then you have multiple people connecting to different instances of node. If you have a pub/sub implementation, subscribers of one node instance won’t hear publishing from the other node instances, and vice versa. Therefore you need something like redis as a common ground so that those pub/subs will be in sync. Also, if you need to reboot a node instance or if it fails for one reason or another, the data in those instances are no more. Definitely not useless in a pub/sub scenario.

  • Colin

    My suggestion if you really needed to scale past one instance was “Even then, you could quite easily have node instances which subscribe to events from the master node server and re-publish all received events.”

    That is, there is no reason that you can’t have a node instance acting as both a server and a client/slave to a master instance with literally only a few extra lines of code. With pub/sub there is no persistent data so rebooting is moot, only availability matters and in my experience Redis is no more stable than Node. Using Redis+Node means two points of failure and two systems to install/configure/update where the job can be done with only one.

    I recently implemented a very similar pub/sub server and opted to do it with pure Node. To each his own.. :)

  • Ah, I think you hit it on the nail. Maybe your pub/sub doesn’t persist data, but mine definitely does :) No chat room app here… if I weren’t persisting data I would agree, no reason for redis/memcached.

  • Ah I think I was misunderstood. I’m actually using nodes pubsub with a redis connection (for the multiple instance availability) since I’m using redis for other stuff. It was far easier to use nodes pubsub vs redis pubsub even though I have access to it, but using the redis conn to share between instances makes it a no-config setup since I’m using redis for other data storing.

  • Joel

    Hello I am new to node.js. I am having some trouble getting this to work. I get this error when I try to start the server.js server

    Joels-iMac:nodejs joelcox$ node server.js
    info – socket.io started

    events.js:72
    throw er; // Unhandled ‘error’ event
    ^
    Error: Redis connection to 127.0.0.1:6379 failed – connect ECONNREFUSED
    at RedisClient.on_error (/Applications/MAMP/htdocs/work2/nodejs/node_modules/redis/index.js:185:24)
    at Socket. (/Applications/MAMP/htdocs/work2/nodejs/node_modules/redis/index.js:95:14)
    at Socket.EventEmitter.emit (events.js:95:17)
    at net.js:441:14
    at process._tickCallback (node.js:415:13)

  • It is trying to connect to redis but the connection was refused

  • Pablo Acuña

    Hi!, very cool tutorial, works like a charm.

    I’m new on this websocket thing. It’s possible to adapt this setup to make notifications only for an specific user and the other users not be able to see the information? or for example one user to other user?
    Thanks!

  • Probably it can but I don’t have a clue how to get it really secure.

  • Pablo

    What I did was create a channel for every user that connects to the application and use a stored secret hash for the name of the channel that is re-generated in every login. Then the notifications are send to this pseudo-private channel. I don’t know if it’s the most secure way but I can’t think in some dangerous scenario.

  • Joel

    I love this It works great. I implemented this in two of my apps. However, I Honestly Consider checking out Angular.js with Firebase.js for the front end of your app. Or use it only for the live content you want to load… like a chat or notifications. It will only take you 15 lines of code.

  • Nice tutorial. But not sure how to use it with redisStore, I’m getting the subscription multiple times!

  • Maybe because I am new to NodeJS and Redis. At some point I am not sure where to put your code. Under the paragraph Laravel, point 7.

    7. Fire event in controller (update score) :
    Event::fire(UpdateScoreEventHandler::EVENT, array($updatedMatch));

    Do I need to put it in a method inside of a desired controller class e.g. ‘UserController@indexPage’?

    Under the paragraph NodeJS, point 3.

    3. /nodejs/npm install socket.io express redis

    I don’t know how to deal with it. Please explain me a bit more. Thank you!

  • 1. Just put the Event:fire in the method which handles the save/update.
    2. It is just how node works. There is a lot of documentation on the internet.

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>