× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

Using socket.io with RethinkDB changefeeds to build a reactive backend

Scott Hasbrouck
Jan 08, 2016
<p><a href="http://rethinkdb.com/docs/">RethinkDB</a> is the latest major entrant NoSQL database. Having used it in production to build my current startup, it is in my opinion one of the best databases for a reactive web app. The key feature that makes it so powerful are changefeeds.<br> <br> Meteor made use of <a href="http://blog.mongolab.com/2014/07/tutorial-scaling-meteor-with-mongodb-oplog-tailing/#Meteor_Oplog_Tailing_Overview">MongoDB oplog tailing</a> to push database changes up to the client (i.e. the client app did not need to regurlalr poll the backend). Building on this idea, this is how I've used <a href="http://rethinkdb.com/docs/changefeeds/javascript/">RethinkDB changefeeds</a> to push database changes to the client of a WebSocket, thus, keeping client and server data in seamless sync!</p> <p><strong>Notes</strong>:</p> <ul> <li>The only langauage used in this tip is JavaScript (node.js)</li> <li>Assuming you already have a working Node app, with the RethinkDB module installed, and connected to a running RethinkDB database. We'll reference 'connection' as the pointer to the RethinkDB connection, and 'r' as the RethinkDB JS library</li> <li>No Client side code is covered, so you could use React, Angular, or any framework that has a client side immutable data store that has getters and setters (i.e. <em><span style="color:rgb(95, 99, 102)">this.setState({})</span></em> and <em>this.state</em> in React.js)</li> <li>Assuming you have some knowledge of Socket.io, and have a socket setup as 'socket'</li> </ul> <p>Let's say we have a table in RethinkDB called <strong>Fruit</strong>, with the documents:</p> <table align="left" border="1" cellpadding="1" cellspacing="1" style="width:500px"> <thead> <tr> <th scope="col">id</th> <th scope="col">type</th> </tr> </thead> <tbody> <tr> <td> e73c1635-88a9-4d27-8c2a-ab447449bb4b</td> <td> Apple</td> </tr> <tr> <td> 86fc5fd7-0ce0-42a5-b9cc-44d2e34c027d</td> <td> Orange</td> </tr> </tbody> </table> <p> </p> <p> </p> <p> </p> <p>We run the following query to get all of the fruit:</p> <pre><code class="language-javascript">r.table('Fruit').run(connection, loadCallback);</code></pre> <p>Now, we want to write a callback to emit an initial array of all of the fruit to the client</p> <pre><code class="language-javascript">var loadCallback = function(err, cursor) { if (err) // throw something // returns an array of all documents (fruit in this case) in the cursor cursor.toArray(function(err, fruit) { if (err) // throw here too socket.emit('load_fruit', fruit); } };</code></pre> <p>Of course, one more layer and we'll officially be in callback hell. So by all means, use promises here.</p> <p>But now we have an array of all of the fruit emited to the client (or whoever happens to be listening to 'load_fruit' on this socket). This is where we would write a listener in our client data store to set the initial value.</p> <p><strong>And now for changefeeds!</strong></p> <p>We make one additional method call to the above query to get the changefeed for a table:</p> <pre><code class="language-javascript">r.table('Fruit').changes().run(connection, updateCallback);</code></pre> <p>And finally, to push updates to our fruit table to any socket listeners, our update callback is as follows:</p> <pre><code class="language-javascript">var updateCallback = function(err, cursor) { if (err) // throw something cursor.each(function(err, fruit) { if (err) // throw here too // handle a new fruit if (fruit &amp;&amp; fruit.new_val &amp;&amp; fruit.old_val === undefined) { // Here, the listener in the client side data // store should push this fruit onto the fruit array socket.emit('new_fruit', fruit.new_val); } // handle update fruit else if (fruit &amp;&amp; fruit.new_val &amp;&amp; fruit.old_val) { // client side listener should look up fruit by id // and replace with this new fruit value socket.emit('update_fruit', fruit.new_val); // handle deleted fruit else if (fruit &amp;&amp; fruit.new_val === null) { // client side listener should look up fruit by id // and delete it // *note* we still pass the old value of the fruit // so that we have it's id to look it up in the client socket.emit('delete_fruit', fruit.old_val); } } };</code></pre> <p>Now, we can add a new Fruit, say a type = 'Banana', and a new_fruit event will be emitted. If we change the Apple into a Pear, an update_fruit event will be emitted. Finally, if we delete the Orange, a delete_fruit event will be emitted with the old values (including the id).</p> <p>And that's the basics of combining RethinkDB changefeeds with Socket.io, to keep client side data stores in sync with the backend!</p>
comments powered by Disqus