We are the Dev Teams of
  • brands
  • ebay_main
  • ebay
  • mobile
<
>
BLOG

Connecting PHP and Node with Redis Pub/Sub and SockJS

by Przemek Matylla
in Tutorials

puzzle-work-togetherLet's face it - modern webdev is all about real-time. You want to get the data as soon as possible, ideally as it's published by users or pushed by data providers. How about we implement very simple, yet powerful applications that utilize realtime communication between each other? Let's say for example we have a PHP front-end that allows users to input some data and on the other end of our stack we have a Node app that needs to get the data from PHP as fast as possible without polling of any kind.

That's right — Redis and SockJS to the rescue!

Firstly lets create a PHP app from which the data will be sent through Redis all the way to Node and then pushed to the front-end in another browser window/tab. There are a lot of PHP micro-frameworks which you can choose from. I picked Slim. Additionally we will need a client library for Redis. Let’s go for Predis.

Prerequisites

This article assumes that you have the following minimum software installed on your server:

  • PHP 5.3.0 or newer
  • Node
  • Nginx/Apache

PHP Application

$ mkdir pubsub-php

Pubsub stands for Publish/Subscribe – a pattern we will use in Redis. Now in the pubsub-php directory we need to install Composer which is a dependency manager for PHP.

$ curl -s https://getcomposer.org/installer | php

Now create a composer.json file and paste the following contents:

{
    "require": {
        "slim/slim": "2.*",
        "predis/predis": "dev-master"
    }
}

Perfect. Lets install the Slim framework and Predis in the project directory:

$ php composer.phar install

Ok, with all the dependencies installed, we can now move on to coding the PHP part. In our project we will need two routes – one for displaying the homepage where a user inputs the name of a person he wants to greet and the second takes the input and publishes it on Redis’ pub/sub channel. Create an index.php file and paste the following:

<?php
require 'vendor/autoload.php';

/*
* Instantiate Slim and Predis
*/

$app = new Slim\Slim();
$redis = new Predis\Client();

/*
* Setup routing
*/

$app->get('/', function() use ($app) {
    $app->render('index.html');
});

$app->get('/greeting/:name', function($name) use ($redis) {
    $value = $redis->publish('updates', $name);
});

$app->run();

The code looks pretty straightforward. We instantiate Slim and Predis and set up the two routes we mentioned earlier. Whenever a user hits the root of our app i.e http://localhost we render a file called index.html in the browser. From the index.html file we send data provided by the user to the /greeting/:name route. Notice the use of a :name here. This is the route parameter which is passed to the associated callback function and is eventually published in Redis. So whenever you GET http://localhost/greeting/Pinkman the value of $name will be “Pinkman” (duh).

The last part of the PHP setup is the index.html page we will display in the browser. Create the previously mentioned file in the root of our app directory and paste in the following contents:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>PHP Part</title>
    <style>
      body {
        font: 24px "Lucida Grande", Helvetica, Arial, sans-serif;
        color: #333;
        background: #fafafa;
      }

      * {
        outline: none;
      }

      #wrap {
        text-align: center;
        padding: 100px 0 0 0;
      }

      input[type="text"] {
        border: 1px solid #ccc;
        border-radius: 3px;
        padding: 10px;
        width: 300px;
        margin: 20px 0 0;
      }

      button {
        padding: 10px;
        border-radius: 3px;
        border: 1px solid #ccc;
        background: #fff;
      }
    </style>
  </head>
  <body>

  <section id="wrap">
    Enter a name of a person you want to greet:<br>
    <input type="text" id="greeting" />
    <button id="trigger">Send Greeting</button>
  </section>

  <script>
    var trigger = document.querySelector('#trigger')
      , input = document.querySelector('#greeting')
      , xhr = new XMLHttpRequest();

    var sendGreeting = function() {
        if (input.value) {
            xhr.open('GET', '/greeting/' + input.value, true);
            xhr.send();
            input.value = '';
        }
    };

    trigger.addEventListener('click', sendGreeting);

    input.addEventListener('keydown', function(e) {
        if (e.keyCode === 13) {
            sendGreeting();
        }
    });
  </script>
  </body>
</html>

As you can see we have setup a basic HTML structure with an input field and a button. At the very bottom we have attached event listeners to the DOM elements in vanilla JavaScript (there’s really no need to use jQuery here). Whenever you type something in the input field and press the button or hit Enter/Return the data will be sent via AJAX to the backend. That’s it – the PHP part is now ready.

PHP-part

Let’s move on to the Node part.

Node Application

Create a new directory “pubsub-node” next to “pubsub-php”:

$ mkdir pubsub-node

and create a “package.json” file inside. Paste the following contents in to this file:

{
  "name": "pubsub-node",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app"
  },
  "dependencies": {
    "redis": "3.1.0",
    "sockjs": "*"
  }
}

Now let’s install the Redis client and the SockJS modules by executing:

$ npm install

At this point I’d like to take a moment to explain the use of SockJS over Socket.io. The latter is now old and the library is no longer maintained. At the time of writing there are 543 opened issues and the last commit was pushed over 8 months ago. On the other hand we have SockJS which does the exact same thing by providing a simple WebSockets API that blurs the differences between browsers by enabling support for different transport mechanisms. It is also actively maintained and pull requests are merged pretty quickly. The ultimate goal of SockJS is not to be used at all. At first glance this may seem strange, but when we look closer we see that it makes perfect sense. The library provides the same API for WebSockets communication as the native WebSockets found in modern browsers. This means that eventually old browsers will slowly fade away or you may choose not to support them at all and remove only the SockJS library and continue using your code with native WebSockets.

Now that we have clarified the situation we can move on to the next section. Create an app.js file in the root of the node app directory and paste in the following code:

/*
* Dependencies
*/

var redis = require('redis').createClient()
  , http = require('http')
  , sock = require('sockjs')
  , fs = require('fs');

/*
* Store all connected clients
*/

var sockets = {};

/*
* Create websockets server and attach listeners
*/

var socketServer = sock.createServer();

socketServer.on('connection', function(conn) {
    sockets[conn.id] = conn;
});

/*
* Cache front-end file
*/

var front = fs.readFileSync(__dirname  + '/index.html', { encoding: 'utf8' });

/*
* Create http server
*/

var httpServer = http.createServer(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end(front);
}).listen(1337);

/*
* Hook websockets in to http server
*/

socketServer.installHandlers(httpServer, { prefix: '/websockets' });

/*
* Subscribe to 'updates' channel
*/

redis.subscribe('updates');

/*
* Push incoming message to all connected clients
*/

redis.on('message', function(channel, data) {
    for (var id in sockets) {
        sockets[id].write(data);
    }
});

The code example above is fairly self-explanatory. The necessary modules are required, we create HTTP and SockJS servers and instantiate the Redis client. At the very bottom we tell the Redis client to listen on the “message” channel for any data that may enter. Once the data arrives we broadcast it to all connected WebSockets clients.

The only missing piece is the front-end file cached on line 30 of the above code. Do not confuse it with the front-end part of the PHP application. The Node part acts as a presenter of the greetings provided by the PHP app. In the root of the Node app create an “index.html” file and paste the contents:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Node Part</title>
    <style>
      body {
        font: 24px "Lucida Grande", Helvetica, Arial, sans-serif;
        color: #333;
        background: #fafafa;
      }

      #wrap {
        text-align: center;
        padding: 100px 0 0 0;
      }

      strong {
        color: #f60;
      }
    </style>
  </head>
  <body>

  <section id="wrap"></section>

  <script src="//cdn.sockjs.org/sockjs-0.3.min.js"></script>
  <script>
    var sock = new SockJS('http://127.0.0.1:1337/websockets')
      , wrap = document.querySelector('#wrap');

    sock.onmessage = function(e) {
        wrap.innerHTML = 'Hello <strong>'+e.data+'</strong>, wow are you doing today?';
    };
  </script>
  </body>
</html>

The skeleton here is pretty similar to the PHP front-end. Here at the bottom of the script we include the SockJS library and connect to the SockJS server listening on the “/websockets” route. Then we setup an event listener for the “message” event. When the message arrives we inject it within the “wrap” section.

Node-part

Go to http://localhost (depending on your Apache/Nginx setup) and in another tab/browser open http://127.0.0.1:1337 (where the Node app lives). Now whenever you type a name in the PHP front-end the data will be sent via AJAX to the backend, published in Redis, received by Node’s subscriber and pushed to the front-end by SockJS. Pretty cool. That way you can connect as many applications as you like as long as they have a decent Redis client available. Have fun with Node and Redis!

Title Graphics: Wikimedia Commons

javascript, ajax, nodejs, redis, sockjs, websockets

?>