Build a realtime liveblog with RethinkDB and PubNub
RethinkDB provides a persistence layer for realtime web applications, but the rest of the stack is up to you. A number of different frameworks and services are available to help you convey individual realtime updates to your application’s frontend.
In this blog post, I’ll demonstrate to use RethinkDB with PubNub, a service that provides hosting for realtime message streams. You can build realtime web application with PubNub and RethinkDB, taking advantage of PubNub’s cloud infrastructure to simplify development and scalability. To demonstrate how you can use PubNub with RethinkDB changefeeds, I’ll show you how to build a simple liveblog application.
Why PubNub?
PubNub offers a number of advantages over implementing your realtime streams by hand or using an open source abstraction framework like Socket.io. Relying on PubNub’s hosted infrastructure obviates a range of scalability challenges, so you can build your application without having to worry about how many simultaneous streams you can accommodate.
Another notable advantage of PubNub is its breadth of support for various platforms and environments. It offers SDKs for multiple programming languages and platforms, which is particularly beneficial when you want to build multiple frontends–spanning the web and native application platforms.
PubNub also provides plugins that cover a range of common realtime usage scenarios, reducing the amount of plumbing that you need to implement yourself. The available plugins provide functionality like mobile push notification support, access management control, and live analytics. In this blog post, I’ll demonstrate how you can use the access management plugin to secure a stream–ensuring that it is only accessible to registered users.
Use PubNub to publish updates from a RethinkDB changefeed
RethinkDB is built for realtime applications: instead of polling for
changes, the developer can turn a query into a live feed that continuously
pushes updates to the application in realtime. In RethinkDB’s ReQL query
language, the changes
command turns an ordinary query into a realtime
changefeed. As you will see, it’s easy to attach a RethinkDB changefeed to
a PubNub data stream.
PubNub uses a straightforward publish/subscribe architecture. When an application publishes messages into a PubNub “channel”, the messages are received by subscribed clients. It’s worth noting that PubNub channel communication is fully symmetrical–your frontend and your backend are both clients that are capable of subscribing and publishing.
When you register your application on PubNub, it will give you a publishing key, a subscription key, and a secret key. A client needs the subscription key in order to read messages and the publishing key in order to post messages. The secret key is used for access management, which I’ll demonstrate later.
In this post, I’m going to show you how to use PubNub’s Node.js client
library, which you can install from npm. The first step is to require and
initialize the pubnub
client module:
var pubnub = require("pubnub");
var pn = pubnub({
subscribe_key: "xxxxxxxxxxxxxxx",
publish_key: "xxxxxxxxxxxxxxx",
secret_key: "xxxxxxxxxxxxxxx"
});
To publish a message from PubNub, call the publish
method and indicate
the channel that you want to use to send the message. Your message can be
a JSON object or an individual value like a string. The following example
shows how to create a changefeed on a RethinkDB table and convey all of
the updates through a PubNub channel:
var pubnub = require("pubnub");
var r = require("rethinkdb");
var pn = pubnub({
subscribe_key: "xxxxxxxxxxxxxxx",
publish_key: "xxxxxxxxxxxxxxx",
secret_key: "xxxxxxxxxxxxxxx"
});
// Connect to a local RethinkDB database
r.connect().then(function(conn) {
// Attach a changefeed to the `updates` table
return r.table("updates").changes()("new_val").run(conn);
})
.then(function(changes) {
// For each change emitted by the changefeed...
changes.each(function(err, item) {
// Publish the change through PubNub
pn.publish({
channel: "updates", message: item,
error: function(err) {
console.log("Failed to send message:" , err);
}
});
});
});
In the example above, the application passes an object with three
properties to the PubNub library’s publish
method:
- The
channel
property tells PubNub to publish the message on a channel calledupdates
. The channel name is arbitrary, but you will need it on the other side in order to subscribe to the messages. - The
message
property is the JSON object that we wish to send through the channel–in this case, it’s the updated object from the RethinkDB table. - The
error
property lets us define a callback function that the library executes when it cannot properly send the message.
Now that we have our backend application configured to broadcast table updates, we need to set up the frontend and make it subscribe to receive the messages. For the purposes of this demo, I’m going to show you how to make a web-based frontend that runs in the browser. Keep in mind that you could easily take advantage of PubNub’s mobile SDKs to make comparable mobile frontends.
I created a simple web page that loads the PubNub JavaScript client
library from PubNub’s CDN, subscribes to the updates
channel, and then
prints each received message to the console:
<html>
<head>
<title>RethinkDB PubNub Demo</title>
<script src="https://cdn.pubnub.com/pubnub.min.js"></script>
<script>
// Connect to PubNub's service
var pn = PUBNUB.init({subscribe_key: "xxxxxxxxxx"});
// Subscribe to the `updates` channel
pn.subscribe({
channel: "updates",
message: function(message, env, channel) {
// Display each message from the channel in the console
console.log("Message:", message);
}
});
</script>
</head>
<body>
...
</body>
</html>
The client only includes the subscription key, not the publication key or the secret key. Anybody can view the source of the page, which means that they can see and access the keys. If we give up the publication key, then somebody might use it to post malicious messages into the application. You should only include the publication key in your client application if you also use the access control plugin to limit publishing.
Build a liveblog app with RethinkDB and PubNub
I used RethinkDB and PubNub to build a simple liveblog tool. The liveblog administrator types in messages, which the tool broadcasts to liveblog attendees. The application uses JSON Web Tokens (JWT) for authentication, ensuring that only the admin can send messages. The backend is implemented in JavaScript with Node.js and Express. The browser-based frontend is built with Vue.js, a lightweight JavaScript MVC framework that supports data binding.
The specifics of JWT authentication are beyond the scope of this article, but you can refer to the complete source code to see how I implemented that part of the application. User information is obviously stored in RethinkDB, with bcrypt for password encryption.
When the PubNub client library receives a new message on the frontend, it appends it to an array. I use simple data binding to display the contents of the array to the end user:
Frontend markup:
<ul id="messages">
<li class="message" v-repeat="messages">
<span class="sender">{{sender}}</span>
<span class="time">{{time | moment '(h:mm A)' }}</span>
<p class="text">{{text}}</p>
</li>
</ul>
Frontend JavaScript:
Vue.filter("moment", function(value, fmt) {
return moment(value).format(fmt).replace(/'/g, "");
});
var app = new Vue({
el: "body",
data: {
messages: [],
},
ready: function() {
var pn = PUBNUB.init({subscribe_key: "xxxxxxxxxxxxxxx"});
var that = this;
pn.subscribe({
channel: "updates",
message: function(message, env, channel) {
that.messages.unshift(message);
}
});
}
});
Of course, that will only display new messages that are sent to the stream
while the user is viewing the page. I also had to add a way for the user
to retrieve the existing messages so that they can see what they missed.
On the backend, I added an /api/history
URL endpoint that provides a
JSON record of current liveblog messages, ordered by timestamp:
app.get("/api/history", function(req, res) {
r.connect(config.database).then(function(conn) {
return r.table("updates").orderBy(r.desc("time")).run(conn)
.finally(function() { conn.close(); });
})
.then(function(stream) { return stream.toArray(); })
.then(function(output) { res.json(output); });
});
On the frontend, the application simply has to perform an XHR request to that endpoint and put the returned JSON objects into the messages array.
To handle message publication, I added a textbox that is only visible to
the administrator. When the user types a message into the box and hits
enter, the frontend send an HTTP POST request to an /api/send
URL
endpoint that I implemented on the backend.
Frontend markup:
<div id="messagebox" v-if="user.admin">
<textarea v-model="message" v-on="keyup:send | key enter" placeholder="Type your message here"></textarea>
</div>
Frontend JavaScript:
var app = new Vue({
...
methods: {
send: function(ev) {
fetch("/api/send", {
method: "post",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + this.token
},
body: JSON.stringify({message: this.message})
})
.then(function(output) { return output.json(); })
.then(function(response) { app.message = null; });
},
...
}
});
You can send that POST request to the /api/send
URL endpoint however you
want, but you’ll notice that I chose to use the W3C Fetch method.
I like using Fetch because its Promise-based output is easy to consume.
Fetch isn’t supported in very many browsers yet, however, so I
use a polyfill to get it today.
My backend code checks to make sure that the user who sent the message is
an admin and then adds the message to the database. We already have a
changefeed on the updates
table wired up to the PubNub channel, so we
know that the application will broadcast it to the frontend.
app.post("/api/send", function(req, res) {
if (!req.user.admin)
return res.status(401).json({success: false, error: "Unauthorized User"});
r.connect().then(function(conn) {
return r.table("updates").insert({
text: req.body.message,
sender: req.user.username,
time: r.now()
}).run(conn).finally(function() { conn.close(); });
})
.then(function() { res.json({success: true}); });
});
That’s pretty much all you need to build a simple liveblog application with RethinkDB and PubNub.
Use PubNub access management to control stream availability
I added a few more features to my application in order to explore PubNub’s more advanced capabilities. I used PubNub’s access management plugin to put a registration wall in front of the liveblog, making it so that only users with accounts are able to see the content.
It’s easy to add an authentication check to the /api/history
method that
I use to provide message backlog. But I needed PubNub’s access control
system in order to protect the stream itself, preventing unauthorized
users from accessing the latest updates.
When the application starts, I have it universally disable reading so that users can’t access the streams without authenticating:
var pn = pubnub({ ... });
pn.grant({
write: true, read: false,
callback: function(c) { console.log("Permission set:", c); }
});
In the backend authentication handling code, which executes when the user
logs in, I use the PubNub library’s grant
method to enable stream
reading for the individual user’s JWT access token:
pn.grant({
channel: "updates", auth_key: acct.token,
read: true, write: false
callback: function(c) { console.log("Set permissions:", c); }
});
On the client side, when the user completes the login process, I give their newly-received access token to the PubNub client library:
var app = new Vue({
...
login: function(ev) {
fetch("/api/user/login", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: this.username,
password: this.password
})
})
.then(function(output) { return output.json(); })
.then(function(response) {
var pn = PUBNUB.init({
subscribe_key: "xxxxxxxxxxxxxxx",
auth_key: response.token
});
...
Now users will only be able to access the message stream if they
authenticate and provide an access token that has appropriate permissions.
If you wanted, you could eliminate the /api/send
method entirely: simply
make the frontend publish the administrator’s messages into a channel
while relying on access control to prevent unauthorized users from sending
anything. I’m going to leave that as an exercise for the reader.
Although there are good libraries like Socket.io available for developers who want self-hosted realtime message delivery, PubNub offers a compelling option for developers who want to offload their realtime streams to the cloud. PubNub’s advanced options and useful plugins can also help accelerate development.
Now that you have seen what you can build with RethinkDB and PubNub, install RethinkDB and check out our ten-minute quickstart guide.
Resources:
- PubNub developer center
- PubNub’s access management plugin docs
- Full source code for the liveblog demo