close

Day 3: Realtime collaborative drawing with Node.js

Drawing Example using Paper.js

My Christmas present to you this year is the power of combining Node.js with WebSockets and HTML 5 Canvas to build a real-time collaborative drawing app similar to draw stuff, my own hack day project (pictured above). It will be a single page with a full screen canvas for all connected users to draw on. Their drawing will be sent in real-time to every other user. It’s going be awesome.

More than this I hope it will open the door to exploring what new and exciting opportunities these relatively new technologies offer.

Along the way we will cover:

  • Installing and running Node.js locally
  • NPM (Node Package Manager) to install libraries and frameworks
  • Express Framework, the popular MVC framework for Node.js
  • Jade, the Node templating engine
  • Socket.IO, the library for real-time communication in JavaScript
  • Paper.js, the popular framework for HTML 5 Canvas

It’s a long list of technologies, some, or all of which may be new to you but DONT PANIC! I won’t assume any knowledge and I’ll talk you through step by step

Requirements

I’ve tried my best to keep this tutorial simple but there are still some requirements:

  • being comfortable in terminal (or at least willing to try)
  • having a basic understanding of JavaScript or comparable language.
  • A modern browser, sorry IE8 fans, you’ll need to switch to a different browser.

I’ll be using OSX Mountain Lion. You’ll find this article the easiest to follow if you do too, but if you are comfortable translating some of the instructions to your operating system, you’ll be fine.

Installing Node.js and NPM

Node.js Logo

The first thing we need is a local installation of Node.js and NPM, so head to the Node.js website and hit the big green install button (I’ll be using version 0.8.15 of Node.js). Download Node.js, open the package and follow the on-screen instructions (I’ve always wanted to write that).

Once Node.js is installed, open your Terminal, Shell or Command Prompt and check if NPM is fit, healthy and raring to go:

$ npm

You should see some help text explaining how to use NPM. NPM is a command line tool for installing ‘node packages’ and it’s fantastic. The first package we will install is the Express framework.

The good news is that even if you only ever get this far you now have access to many modern day JavaScript libraries, frameworks and tools. In fact, some have even gone as far as to say that NPM is part of the new baseline for front end developers.

Hello World: Installing Express Framework

Express Framework

To run the Express framework we will first install it globally. This means we will be able to make a new Express instance wherever we want. Try the following command (entering your password if it needs it):

$ sudo npm install -g express

Depending on your operating system and security settings you may or may not need sudo at the front of the command. If the command didn’t work try it without sudo. Chances are you will get a few warn messages, but there should be no errors.

Now let’s create a new instance of the Express framework. The first step is to change directory to where your application will be. Try these commands in terminal:

$ cd
$ cd Sites/
$ mkdir 12devs
$ cd 12devs

If these commands don’t work for you its probably because your folder structure is different. You need to decide where you want to create your application and then navigate to that folder in terminal. If you are unsure how to do this, sections 1.2 and 1.3 of this tutorial should help.

If the previous commands worked you have just created a directory called 12devs and changed into it. That means you are now located inside the 12devs. Next we will tell Express to create a new folder with all the files it needs to run itself locally:

$ express 12devsNodeJsDraw

The Express Framework has dependencies, so let’s change into the applications folder and tell NPM to install these dependencies (remembering to remove sudo if its not needed):

$ cd 12devsNodeJsDraw
$ sudo npm install

You now have a working instance of the Express framework setup and ready to go. Before we power it up there’s one more package I’d like you to install; nodemon by Remy Sharp. You can read what it does in the readme if you want, but the short explanation is that it will run Node.js and automatically restart our server every time we update the application code. So let’s install it globally using NPM:

$ sudo npm install nodemon -g

Again, you may or may not need the sudo at the start of the command. Time to power her up!

$ nodemon app.js

Browse to http://127.0.0.1:3000/. Congratulations! You are now running the Express framework on Node.js on your local machine. But what is actually running? Let’s take a look inside the application and summarise the important stuff.

  • node_modules/: The framework’s files and dependencies such as Jade
  • public/: Static files to be downloaded by the browser, such as images, JavaScript and stylesheets
  • routes/: Definitions of the different URLs the application can respond to
  • views/: The Jade templates that build the html files which will be served to the user
  • app.js: The core code file for your application. This also sets up the Express framework
  • package.json: Information about the application, including dependencies

That’s all the detail I’m going to go into about the Express framework. But if you want more detail you might find the blog post Web Applications with Express and Node.js an interesting read.

The fantastic news is that not only can you use NPM to download JavaScript libraries, but you’ve now made your first ‘hello world’ program in Node.js. Could this Christmas get any better?

Installing Socket.IO

Socket.IO logo

I still remember laughing wickedly the first time I sent data from browser A, to the server, to browser B in real time using Socket.IO. I also remember being surprised at how simple it was. I hope it might have the same effect on you

To make things easier, open a second Terminal, Shell or Command Prompt window, so that you can leave nodemon running in the background. In the new window, navigate to your project directory (the one we looked at above with the app.js file in it), and install Socket.IO:

$ npm install socket.io express

This downloads Socket.IO to the node_modules folder so that we can configure the server and the client. If you were following the official Socket.IO documentation this might be the first time you’d notice it is not quite up to date. Express has changed and so has the way we configure Socket.IO, but fortunately it’s still quite simple. Open up /app.js and change the last 3 lines of Javascript (starting with http.createServer(app)) to:

var server = http.createServer(app).listen( app.get('port') );
var io = require('socket.io').listen(server, function() {
        console.log("Express server listening on port " + app.get('port'));
});

Alternatively you may wish to replace your app.js with the contents of my version. Now we need to enable Socket.IO on the client. Open up /views/layout.jade and just below the line containing:

link(rel='stylesheet', href='/stylesheets/style.css')

add:

script(src="/socket.io/socket.io.js")
script(src="/javacripts/socket.js")

What you are looking at is Jade, the templating engine for Node.Js that’s similar to HAML for Ruby. By replacing a closing tags with tab indentation and simplifying the syntax JADE makes HTML easier to write. However, you need to be careful with your indentation. You must ensure that the new script elements are indented the same amount as the link element and that the entire page uses tabs or spaces for indentation, never both. Save your updates, browse to http://127.0.0.1:3000/ and if you see an error that mentions tab indentation, review the tabs in your Jade template.

The last step is to setup the Socket.IO connection on the client. Create a file called socket.js in the javascripts/socket.js file. First we need to create a global variable called io that is a Socket.IO connection to the server:

// Connect to the Node.js Server
io = io.connect('/');

Now let’s test that Socket.IO is working correctly by sending a message from the browser, to the server and back to the browser again. For this we will use 2 events ping and pong. When the browser sends a ping event to the server, it will emit a pong event back to the browser. The two will live in perfect harmony emitting ping and pong events for as long as we want. Add the following JavaScript to the end of /public/javascripts/socket.js:

// (1): Send a ping event with 
// some data to the server
console.log( "socket: browser says ping (1)" )
io.emit('ping', { some: 'data' } );

// (4): When the browser receives a pong event
// console log a message and the events data
io.on('pong', function (data) {
    console.log( 'socket: browser receives pong (4)', data );
});

and add the following JavaScript to the end of the app.js file:

// A user connects to the server (opens a socket)
io.sockets.on('connection', function (socket) {

    // (2): The server recieves a ping event
    // from the browser on this socket
    socket.on('ping', function ( data ) {
  
    console.log('socket: server recieves ping (2)');

    // (3): Return a pong event to the browser
    // echoing back the data from the ping event 
    socket.emit( 'pong', data );   

    console.log('socket: server sends pong (3)');

    });
});

Now open up the Terminal, Shell or Command Prompt where you have nodemon running. Refresh the page (http://127.0.0.1:3000/). You should see two messages in the browser’s console and (amongst others) 2 messages in the terminal. They should look like these screenshots:

Messages in the browsers console
Messages in terminal

These messages happen almost instaniously, but if we were to slow the process down we would see that they happen in the following order:

  1. Browser says ping (1)
  2. Server receives ping (2)
  3. Server sends pong (3)
  4. Browser receives pong (4)

You can also see the data we are sending from the browser to the server with the ping event (an object with some data in it) is being immediately sent back to the browser with the pong event.

We can extend the server’s response so that instead of just sending the pong event back to our browser, we can emit it to all browsers that are attached to the page. To do this, replace:

// (3): Return a pong event to the browser
// Pass the data from the ping event back again
socket.emit( 'pong', data );   

console.log('socket: server sends pong (3)');

with:

// (3): Emit a pong event all listening browsers
// with the data from the ping event
io.sockets.emit( 'pong', data );   

console.log('socket: server sends pong to all (3)');

This is the fundamental magic behind our collaborative drawing tool; sending data from one browser to the server and then back to every other browser in real-time. And you’ve already nailed it. Congratulations.

To demonstrate this, open up another browser tab, keeping the first open. Browse to http://127.0.0.1:3000/ and open the console. You will see the same ping, pong messages. Now open up the first tab there are 2 new ping, pong messages. The events were sent from the second browser tab, to the server, to both the first and second browser tab. Magic.

Real-time communication like this will become the norm (it already is on some websites), so pat yourself on the back for taking the plunge when you could be eating mince pies and watching Die Hard for the 50th time (thats what I do at Christmas anyway). This is seriously cool stuff you are doing and it’s about to get even cooler.

Let’s Get Artistic

drawings with Paper.js

I don’t mean let’s all drink Absinthe and chop an ear off. I mean our real-time drawing application needs a canvas. We need to be able to draw! We are going to be using Paper.js to handle the canvas interaction, so take some time to look over the examples and then download the library and save the paper.js file from the lib/ folder into your public/javascripts/ folder. You should also create another JavaScript file in the public/javascripts folder called draw.js

We’ll need to include Paper.js by adding the following script elements to views/layout.jade beneath the two existing script elements:

script(src="/javascripts/paper.js")

We don’t need the contents of /views/index.jade any more, so let’s replace it with canvas and script elements. The entire file should look like:

extends layout
block content
    canvas(id="draw", resize="true")
    script(type="text/paperscript", src="javascripts/draw.js", canvas="draw")

You may notice some non-standard attributes on the canvas and script elements:

  • resize="true" tells Paper.js to keep the canvas the size of the browser.
  • type="text/paperscript" tells Paper.js that this JavaScript is to be interpreted by Paper.js.
  • canvas="draw" tells Paper.js to use this JavaScript to draw on the canvas with an ID of draw.

If you open up your browser and inspect the page in the Element inspector you will notice the canvas is the width of the screen and that the body element has a lot of padding. You could even go a little crazy and add an inline style tag to the body… treat yourself, it is Christmas after all.

Now, let’s give our user a paintbrush. I’m going to be using the 2nd from last example on the creating mouse tools example which consists or circles that get smaller or larger depending on the speed of mouse movement.

Drawing Example using Paper.js

So let’s grab the JavaScript from the Paper.js website and make it slightly more artistic. Our paintbrush will paint lots of semi transparent circles. The faster the user moves their mouse (or finger) the larger those cirlces will be. But we don’t want them to be too large, so let’s set a maximum.

tool.maxDistance = 50;

The demo on the website is also quite boring. Wouldn’t it be more interesting if it always painted a random colour? We’ll need a function to pick one:

// Returns an object specifying a semi-random color
// The color will always have a red value of 0
// and will be semi-transparent (the alpha value)
function randomColor() {
    
    return {
        red: 0,
        green: Math.random(),
        blue: Math.random(),
        alpha: ( Math.random() * 0.25 ) + 0.05
    };
}

Lastly, we need to tell Paper.js to paint a circle every time the user clicks and drags their mouse (or finger across the screen). We will specify the circle’s radius and location on the canvas, using our random color function above.

// every time the user drags their mouse
// this function will be executed
function onMouseDrag(event) {
    // Take the click/touch position as the centre of our circle
    var x = event.middlePoint.x;
    var y = event.middlePoint.y;
    // The faster the movement, the bigger the circle
    var radius = event.delta.length / 2;
    // Generate our random color
    var color = randomColor();
    // Draw the circle 
    drawCircle( x, y, radius, color );
    // Pass the data for this circle
    // to a special function for later
    emitCircle( x, y, radius, color );
} 
 
function drawCircle( x, y, radius, color ) {
    // Render the circle with Paper.js
    var circle = new Path.Circle( new Point( x, y ), radius );
    circle.fillColor = new RgbColor( color.red, color.green, color.blue, color.alpha );
    // Refresh the view, so we always get an update, even if the tab is not in focus
    view.draw();
} 
 
function emitCircle( x, y, radius, color ) {
    // We'll do something interesting with this shortly...
}

Et voilà. We are no longer frustrated artists. We have a way to Express ourselves, and Express ourselves we shall (in an abstract, semi-pointilistic sort of way)!

Drawing example

Feel free to play around with the colours, sizes and … well anything else you want at this point. You could try using squares instead of circles, changing the maximum sizes, having the speed affect the color as well as the size. The beauty of this kind of work is that you have no idea how it will turn out. Sometimes you change a value and it’s horrible. Other times it’s beautiful. You can never really predict the outcome.

The next step is to make it collaborative, because it’s all very well drawing away on our own but it’s sort of lonely unless the world can see our creations.

Making It Collaborative

This is where stuff gets really interesting. I know, I know, you didn’t believe it could get more interesting, but it does! It’s time to make it collaborative. Remember how we sent a ping to the server and the server responded by sending a pong back to every browser that’s connected? Well it’s time to replace ping and pong with drawing data.

Let’s take a look at public/javascripts/draw.js, specifically the emitCircle function and replace it with this:

// This function sends the data for a circle to the server
// so that the server can broadcast it to every other user
function emitCircle( x, y, radius, color ) {

    // Each Socket.IO connection has a unique session id
    var sessionId = io.socket.sessionid;
  
    // An object to describe the circle's draw data
    var data = {
        x: x,
        y: y,
        radius: radius,
        color: color
    };

    // send a 'drawCircle' event with data and sessionId to the server
    io.emit( 'drawCircle', data, sessionId )

    // Lets have a look at the data we're sending
    console.log( data )

}

You can see that when the user drags, we are constructing an object that contains the circle’s radius, location (x and y co-ordinates) and colour. Browse to http://127.0.0.1:3000/ and open the console, now draw something on the page. You should see something similar to:

Drawing data in the browsers console

This data is all that’s required to draw the circles on the canvas, but it’s also all that’s required to draw the circles on someone else’s canvas. So let’s make it happen. We are already sending the circle’s data to the server, we just need to then send it on to every user. But first let’s check the server is receiving the drawCircle event correctly. Add this JavaScript to the bottom of our io.sockets.on function in app.js. Ensure that it’s added just before the final });:

socket.on( 'drawCircle', function( data, session ) {
    console.log( "session " + session + " drew:");
    console.log( data );
})

Now switch to the browser, draw something and then open up the terminal. You should see lots of messages like this:

Terminal message displaying the draw data

These messages tell us that the data for the drawing has made its way from the user to the server. Which is fantastic news. Now the server needs to send it back to any other users that are connected. So, in app.js, let’s edit socket.on( 'drawCircle'… a little:

socket.on( 'drawCircle', function( data, session ) {
    socket.broadcast.emit( 'drawCircle', data );
})

Now when the server receives a drawCircle event we are broadcasting the event and its data. Broadcasting means sending an event to every connection (user), except for the conection (user) that created it. We’ll be using this drawCircle event to tell Paper.js to … you guessed it, draw a circle, but we don’t want it to draw a circle the user has already drawn. That’s why we broadcast the event, rather than emit it.

So, the server is broadcasting the event, but the browser isn’t listening for it, which is kind of sad really. The server is just broadcasting merrily away, but no one’s listening. Let’s do our server a favour and fix that before it gets lonely. Open up draw.js and add the following JavaScript:

// Listen for 'drawCircle' events
// created by other users
io.on( 'drawCircle', function( data ) {
    console.log( 'drawCircle event recieved:', data );
})

Now open two browser tabs to http://127.0.0.1:3000/ and make sure the console is open in both. In the first tab draw something quite small. Switch to the second tab; in its console you should be able to see the draw data from the first tab:

Console messages showing the data has been sent

You have successfully sent draw data in real-time from one browser to the other. The last step is to actually draw it, which is something I know you’ll be eager to do. Change the last block of JavaScript we added to draw.js:

// Listen for 'drawCircle' events
// created by other users
io.on( 'drawCircle', function( data ) {

    console.log( data );

    // Draw the circle using the data sent
    // from another user
    drawCircle( data.x, data.y, data.radius, data.color );
    
})

Here we’re using the location, the radius, and the colour from the data sent by the other user to draw a circle that is an exact replica of the circle the other user drew. Let’s try it out. Refresh both tabs and draw something obvious in tab 1. Switch to tab 2, and draw something different. woah You appear to have an exact same drawing inboth tabs!? The circles you drew in tab 1 have been sent from tab 1, to the server and on to tab 2, and vice versa. It will work for any number of tabs too. Cool Huh!.

What’s next?

First things first, a massive congratulations if you made it this far. This was a long tutorial with complex and varied technologies and pitfalls at every turn. But what’s cooler than your enthusiasm and persistence is that you now know how to create real-time applications using Socket.IO and Node.js. I chose to make this real-time application a drawing app, but your next one could be something entirely different. How imaginative can you be?

You might also want to add more features to your app. It would be cool if users could:

  • Choose the shape they want to draw (or pen type)
  • Choose a color
  • Use writing tools
  • Erase part of the drawing
  • Add special effects, stuff like explosions, fireworks, rainbows
  • Save your drawings
  • Create private channels for just two collaborators
  • Store the data
  • Could you add this technology as a bookmarklet so you can draw over a website?

I’m guessing also you would like to deploy your app. Fortunately this blog post is an excellent guide as it’s tailored to the Express framework and Socket.IO. However you may want to try nodejitsu or nodester as Heroku doesn’t allow true web sockets (just long polling)

I’ve also put my version of the application online. You can grab it on github

Have fun and let me know how you get on!

Thanks

This article would not have been nearly as polished without the very generous support of these lovely people. I am incredibly grateful to:

  • James Rutherford develops Node.js, PHP and front-end for start-ups, and co-founded Wedding Tales. He likes home-grown projects, hack days, and quirky data visualisation.
  • Rick Trotter is a PHP developer with a background in systems support, network management, web programming and teaching. He lives in the north east of England and he writes books, develops open source software and enjoys life with his wife and two children.