October 28, 2012
8:36pm

Talking to Drupal with Node.js

As I've mentioned in a previous post sending messages from Drupal to Node is fairly easy to accomplish. However, that is only part of the equation. If we're really wanting to build a larger, more complex Node application, it won't be long before we need the client to talk to the Node server and possibly the Node server to talk to Drupal. At a glance it isn't terribly obvious how to do this with the nodejs integration module, but it turns out there are a few tools tucked in the module code that come to the rescue.

This post will assume some familiarity with the use of the server extension functionality built into the nodejs module. If you are unfamiliar with this, have a look at the example server extension included with the module. In short it does what you might think, allowing other modules to extend the nodejs server setup by the nodejs module. For context I've posted a fully commented example module to GitHub from which the code snippets of this post are taken.

If you are familiar with writing node apps with Socket.io then you are likely already familiar with client side communication back to the node server. Typically you will have a socket.emit on the client side, and a listener to handle the message event on the server side. The same is true when using the nodejs module. In this example I'm building a very simple form with Drupal and displaying it on a page. When the submit button is hit, a bit of client side javascript emits a message through socket.io which is in the Drupal.Nodejs object.

// A simple message.  The listener on the server side does not like us to
// specify 'messageType' so we're calling it 'type' for now.
var message = {
  type: 'nodeToDrupal',
  messageBody: 'Hello from the client side!'
};

Drupal.behaviors.sendDrupalMessage = {
  attach: function(context, settings) {
    // Have our message emitter fire on form submission.
    $("#node-to-drupal-form").submit(function() {
      Drupal.Nodejs.socket.emit('message', message);
      return false;
    });
  }
}

On the server side, in our server extension file, we need to catch our message. This is done in a fairly standard way, the event we are listening for is client-message.

 
exports.setup = function (config) {
  process.on('client-message', function (sessionId, message) {
    // Logging the message to the console.
    console.log('Got a message from the client.  Take a look: ');
    console.log(message);
  });
}

If a client message comes through, we log it to the console. For a lot of node applications our worries are over. We have a way for the client to send a message to the node server. From there we can have the node server do whatever we would like with it. Maybe spit a message back out to the client, to a different session, to a Redis store, or any number of other things. Now the big question. What if we want the node server to send a message to Drupal? There are several reasons we may want to do this, but chief among them would be a database write. The idea of having node write directly to the Drupal database, while possible, would surely harm large baskets full of kittens. It's a bad idea. So we need node to tell Drupal to do its bidding, and for that we need to look at a hook_nodejs_message_callback. This hook allows us to assign handler functions to various message types that being emitted from the node server, and is wonderfully simple.

function node_to_drupal_nodejs_message_callback($type) {
  switch($type) {
    case 'nodeToDrupal':
      return array('node_to_drupal_handler');
  }
  return false;
}

The hook is fired on an sendMessageToBackend call from the node server and passes in messageType from the message. In our case the type is nodeToDrupal (more on this in a bit). The hook is expected to return and array of function names, that will subsequently be called. In this case node_to_drupal_handler.

function node_to_drupal_handler($message, &$responce) {
  // Grab our messageBody from the client message and post it to the watchdog.
  watchdog('node_to_drupal', $message['messageBody'], array(), WATCHDOG_INFO);
  // Tell node we got the message.
  $responce = 'Message logged to Watchdog!';
}

The handler functions called from hook_nodejs_message_callback expect two parameters. The message from node (as an array), and a response passed by reference. In this example we're make a simple watchdog entry of the messageBody, and adding a string to the response stating that Drupal got the message.

Going back to our node server extension, we're going to fire the message we got from the client over to Drupal. To do this were going to use a handy function in our config variable called sendMessageToBackend. Our server extension code is now looking like:

exports.setup = function (config) {
  process.on('client-message', function (sessionId, message) {
    // Logging the message to the console.
    console.log('Got a message from the client.  Take a look: ');
    console.log(message);

    // Insuring that messageType is set before passing along.
    message.messageType = message.type;
    // Send the message to Drupal.
    config.sendMessageToBackend(message, function(error, responce, body) {
      if(error) {
        console.log('Error sending message to backend.', error);
        return;
      }
      // Drupal got it! Output the responce.
      console.log('Response from drupal: ', body);
    });
  })
};

We grab our message from the client and send it to the Drupal backend, then upon success log Drupal's response to the console. Now our little message chain is complete! We have a message coming from the client side to the node server upon a user action. Then node passes that message along to Drupal which records it in the watchdog. This means that not only can we have our node server interacting back and forth with the client, but also have tremendous flexibility over how the node server can interact with Drupal.

Ryan Oles theoleschool.com