WebRTC is a technology that enables real-time communication between web browsers. It is relatively new, and the API definition is still considered to be a draft. Coupled with the fact that WebRTC is not supported by all major web browsers yet, (and among the ones that do, some of them do not support every feature of this technology), this makes it relatively difficult to use WebRTC for any mission critical applications. Or so you would think!

Connect Four over WebRTC using PeerJS: Look ma, no server!

Connect Four over WebRTC using PeerJS: Look ma, no server!

Since it was first introduced by Google in May 2011, WebRTC has been used in many modern web applications. Being a core feature of many modern web browsers, web applications can seamlessly take advantage of this technology to deliver improved user experience in many ways. Video streaming or conferencing applications that don’t require bloated browser plugins, and can take advantage of peer-to-peer (P2P) networks (while not transmitting every bit of data through some server) is only a part of all the amazing things that can be achieved with WebRTC.

In this article, we will take a look at how WebRTC can be used to make a simple P2P web game of Connect Four. To work around the various rough edges and implementation differences of WebRTC, we will use an amazing JavaScript library: PeerJS.

Data over WebRTC

Before we start, it is important to understand that WebRTC is not all about transmitting audio and video streams. It also provides support for P2P data channels. These channels come in two variations: reliable and unreliable. As one may guess, reliable data channels guarantee that messages are delivered and they are delivered in order, while unreliable channels provide no such guarantees.

WebRTC infrastructure - an ocean of acronyms

WebRTC infrastructure - an ocean of acronyms

Moreover, WebRTC data channels require no special infrastructure setup, other than what is needed by a typical WebRTC peer connection: a signaling server to coordinate the connection between peers, a STUN server to figure out public identity of the peers, and optionally a TURN server to route messages between peers if a direct connection between peers cannot be established (for example when both peers are behind NATs). If these acronyms sound familiar, it is because WebRTC repurposes existing technologies wherever possible.

This opens the door to a lot more use cases of WebRTC, including but not limited to multiplayer games, content delivery, and file sharing. Again, all without the need of any intermediary server and hence with lower latencies.

In our simple web game, we will use a data channel between two web browsers to communicate player moves back-and-forth.

Meet PeerJS

PeerJS takes the implementation of WebRTC in your browser and wraps a simple, consistent, and elegant API around it. It plugs various holes in WebRTC implementation of earlier browsers. For example, in Chrome 30 or older, only unreliable data channels were available. PeerJS, if configured to use reliable data channels, would use a shim for those older browsers. Although this wouldn’t be as performant as native implementation of reliable channels, it would still work.

With PeerJS, identifying peers is even simpler. Every peer is identified using nothing but an ID. A string that the peer can choose itself, or have a server generate one. Although WebRTC promises peer-to-peer communication, you still need a server anyway to act as a connection broker and handle signaling. PeerJS provides an open source implementation of this connection broker server PeerJS Server (written in Node.js), in case you do not want to use their cloud-hosted version (which is free right now, and comes with some limitations).

Connect Four Goes P2P

Now that we have a source of confidence for working with WebRTC, i.e PeerJS, let us start by creating a simple Node.js/Express application.

npm init
npm install express --save
npm install jade --save
npm install peer --save

We will use this only to host PeerJS Server, and serve a page and front-end assets. We will need to serve only a single page, and this will contain two sections: a plain main menu, and a 7-by-6 Connect Four grid.

PeerJS Server

Hosting our own PeerJS Server is really easy. The official repository on GitHub even has a one-click button to deploy an instance of PeerJS Server to Heroku.

In our case, we just want to create an instance of ExpressPeerServer in our Node.js application, and serve it at “/peerjs”:

var express = require('express')
var app = express()
// … Configure Express, and register necessary route handlers
srv = app.listen(process.env.PORT)
app.use('/peerjs', require('peer').ExpressPeerServer(srv, {
	debug: true

PeerJS Client

With PeerJS Server up and running, we move on to the client side. As discussed earlier, PeerJS identifies peers with unique IDs. These IDs can be generated by PeerServer for every peer automatically, or we can pick one for every peer while instantiating Peer objects.

var peer = new Peer(id, options)

Here, id can be omitted altogether if we want the server to generate one for us. In our case, that is what we will want to do. PeerServer will ensure that the IDs it gives out are unique. The second argument, options, is usually an object containing key (the API key, if you are using cloud-hosted PeerServer, or host, port, path, etc in case you are hosting the PeerServer yourself).

var peer = new Peer({
	host: location.hostname,
	port: location.port || (location.protocol === 'https:' ? 443 : 80),
	path: '/peerjs'

In order to establish a connection between two PeerJS peers, one of the peers must know the ID of the other peer. For the sake of keeping things simple, in our implementation of Connect Four over WebRTC, we will require the player starting the game to share his peer ID with his opponent. With the destination peer ID known, a simple call to peer.connect(destId) is all that we will need:

var conn = peer.connect(destId)

Both the Peer object and the DataConnection object returned by peer.connect(destId) emit some really useful events that are worth listening on. For the purposes of this tutorial, we are particularly interested about the ‘data’ event of DataConnection object and ‘error’ events of both objects.

In order to send data to the other end of the connection, simply invoke conn.send(data):


Although a bit overkill for our needs here, PeerJS transmits data between peers after encoding them in BinaryPack format. This allows peers to communicate strings, numbers, arrays, objects, and even blobs.

To receive incoming data, simply listen for ‘data’ event on conn:

conn.on(‘data’, function(data) {
	// data === 'hello'

And that is pretty much all we need!

Game Logic

The first player, one who starts a game, is shown his peer ID as generated by PeerJS which they can share with their opponent. Once an opponent joins the game using the first player’s peer ID, the first player is allowed to make a move.

Connect Four, being a game of simple rules and mechanics, has only one type of move: each player, in turn, must pick a column and drop a disc into it. This means that all a peer needs to communicate is the column number in which the current player has chosen to drop his disc in. We will transmit this information as an array with two elements: a string ‘move’, and a number - 0-based index of the column from the left.

Every time a player clicks on a column:

if(!turn) {
	// it is not the current player’s turn

var i
// i = chosen column index
if(grid[i].length == 6) {
	// the column doesn’t have any more space available

// track player’s move locally

// end current player’s turn
turn = false

conn.send(['move', i])

After sending this move data to the opponent, we update the game’s state locally. This includes determining if the current player has won, or if the game has ended in a draw.

On the receiving end of this move data:

if(turn) {
	// ignore incoming move data when it is the current player's turn

var i = data[1]
if(grid[i].length == 6) {
	// ignore incoming move data when it is invalid

// track opponent’s move locally

// activate current player’s turn
turn = true

And naturally, after this we update the game’s state locally, determine if the opponent has won or if the game has ended in a draw.

Notice how we need to perform sanity checks on incoming data. This is important since with WebRTC-based games, we do not have intermediary server and server-based game logic validating the move data.

To keep the snippets simple, lines of code that update the UI have been omitted. You can find the full source code for the client-side JavaScript here.

Connecting It All

To combine it all, we create a simple page with two sections. On page load, the section containing the main menu is shown, the section containing the game grid is kept hidden.

			h1 Connect Four
			div.no-support(style='display: none;')
					p Unfortunately, your web browser does not <a href="http://iswebrtcreadyyet.com">support WebRTC</a>
				a.btn.btn-primary.btn-lg(href='#start') Start
				| &nbsp;
				a.btn.btn-default.btn-lg(href='#join') Join

section#game(style='display: none;')
			h1 Connect Four
					for i in [0, 1, 2, 3, 4, 5]
							for j in [0, 1, 2, 3, 4, 5, 6]

Making these DOM elements look pretty is beyond the scope of this tutorial. Hence we will resort to our trusted companion Bootstrap and do some light styling over it.

As the first player clicks on the “Start” button, the game grid is revealed along with the player’s peer ID. The player can then share this peer ID with their opponent.

Start a new game, and share your peer ID as generated by PeerJS

The second player can click then click on the “Join” button, enter the first player’s peer ID, and begin the game.

Use your opponent's peer ID to join a game

Trying It Out

You can try out this example application at https://arteegee.herokuapp.com.

Or, you can clone the repository from GitHub, install NPM dependencies, and try it out locally:

git clone https://github.com/hjr265/arteegee.git 
cd arteegee
npm install
PORT=5000 npm start

Once the server is running, you can point your web browser to http://localhost:5000, start a game from one tab, and join from another tab (or even a different WebRTC capable web browser) using the peer ID.

You can open your web browser’s console to see some debug information, as in this example application, PeerJS client has been configured to perform verbose logging.

But It Doesn’t Work for Me!

There are two major reasons why this game may not work on your computer.

It is possible that you are using a web browser which doesn’t support the necessary WebRTC APIs yet. If that is the case, you may want to try a different browser - one that supports WebRTC and data channels.

If you are using a modern web browser with WebRTC support, then there is the chance that you are behind some network infrastructure which WebRTC cannot penetrate. Ideally this issue can be easily addressed with a TURN server, but since the example application is not using one, it won’t work when both you and your opponent are behind NATs.


WebRTC is a new technology, and its implementations are quite far from being mature. These often cause some unique challenges for developers. However, with the availability of libraries like PeerJS that neatly abstracts away the rough raw APIs, the technology is becoming quite accessible already.

I hope this brief tutorial for building a PeerJS based game will help you get started with WebRTC and build some amazing real-time peer-to-peer web applications.

About the author

Mahmud Ridwan, Bangladesh
member since December 31, 2013
Mahmud is a software developer with a knack for efficiency, scalability, and stable solutions. With years of experience working with a wide range of technologies, he is still interested in exploring, encountering, and solving new and interesting programming problems. [click to continue...]
Hiring? Meet the Top 10 Freelance WebRTC Developers for Hire in October 2016


Randell Jesup
Note: you only need TURN if you're *both* behind symmetric NATs; normal cone/etc NATs won't force used of TURN. Maybe 5-15% of pairings of users would need TURN to talk to each other.
Pablo Ferreira
WebRTC is the tecnology behind the Firefox Hello, I think. I'd like that the ideia to becomes true.
Jeff Parr
Between "Hosting our own PeerJS Server is really easy." and "With PeerJS Server up and running" ... you are missing at least 5 steps. Especially for Win based localhosted peerJS server. These missing steps degrade your - otherwise good article value by factor of 10.
Andrea Bisello
Hi . Thanks for this tutorial. I'm not able to send data from a peer to another peer. I'm trying in the same chrome (your game works) with two tabs, as for your game (that works). i load the webrtc library from https://cdn.rawgit.com/peers/peerjs/master/dist/peer.min.js because the original host (peerjs) looks death. i opened two chrome tabs, one left and one right so i created two peers, called left and right, and i obtain "open" event response, so i think server given me a id peer.on('open', function(id) { console.log('My peer ID is: ' + id); }); var peer = new Peer('left') //My peer ID is:left and the same for the right tab, with right id. then i register "connection" event and it works : peer.on('connection', function(conn) { console.log("something connected to me",conn); }); so i think connection was good thats because toright = peer.connect('right'); , right obtains "something connected to me" with "conn" object full of informations now i register "data" events toright.on('data', function(data) { console.log('data received'); }) and with the other tabs i try to send data toright.send('hello') but "data received" doesn't come. whats wrong? i attached a screenshot. thanks.
comments powered by Disqus
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering and design posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant technologies
About the author
Mahmud Ridwan
Python Developer
Mahmud is a software developer with a knack for efficiency, scalability, and stable solutions. With years of experience working with a wide range of technologies, he is still interested in exploring, encountering, and solving new and interesting programming problems.