New horizons with Horizon, RethinkDB and React

github twitter github

Imagine if you didn't need to write your backend because you got it for free. If all you needed to do was write your front-end app and connect it to a ready-to-use and very flexible backend. If you know platforms like Meteor or Firebase, you can probably imagine it with ease. But as we all know, every tool has its restrictions. Firebase is not open source (as far as I know), and in the long run, it could not be enough. Meteor is too monolithic and not very flexible. I’m not only saying what I’ve heard. I’ve been active in the Meteor community since Meteor version 0.6.

So why not discover new horizons? Maybe it’s time?

In this article, I want to take a closer look at Horizon - a real-time, open-source backend for JavaScript apps. It was built by the RethinkDB team.

I want this article to be just an introduction to Horizon and RethinkDB, but we'll be learning it by looking at a simple TODO app built with Horizon, React, and Webpack. It will be a ready-to-use boilerplate too. You'll find a lot of cool stuff from my react-boilerplate here because I've used it as a base for the client-side app. So, I hope that it will be more than just some boring info about Horizon and RethinkDB.

What we'll cover here:

  1. RethinkDB – why is there so much hype about it now?
  2. Horizon – quick introduction
  3. react-hz – like Redux, but in the Horizon world
  4. React/Webpack/Horizon TODO app example

RethinkDB – why is there so much hype about it now?

Before we look into the codebase, I need to provide some basic info about RethinkDB and Horizon. To be clear, I don't know RethinkDB very well (yet!), but I can already see why it is gaining popularity so fast. First of all, it’s specially designed to be a database for real-time apps. What does that mean? It has mechanisms that allow the database to push the changes to your app. You don't need to do inefficient polling or use other solutions like Oplog in MongoDB. For sure, it’s crucial when you want to build a bigger real-time app. I don't want to go deep into this here, so if you're going to learn why this is so good, I encourage you to read a blog post: Advancing the real-time web.

The second cool thing is its query language, which is readable and clean. RethinkDB is a perfect mix of NoSQL and SQL-like databases. RethinkDB doesn't support SQL, but its query language can do nearly anything SQL can do, which in my opinion, is a solid point. If you already know SQL, a fantastic cheat sheet compares SQL and RethinkDB's ReQL language: SQL to ReQL cheat sheet.

Another RethinkDB advantage is its Data Explorer. It is a very nicely presented app which is hosted for you when you run your Horizon app. You can read many interesting statistics there and you can also manage your data. Check out the introduction video:

Ok, enough about RethinkDB. It probably needs a separate article, and this one isn't about it for sure. Here we’ll only be using it through Horizon's API, and we’ll be storing our todo items in it. I hope I'll be able to write more about it soon. For now, let's see what Horizon is.

Horizon – quick introduction

Horizon is a real-time JavaScript backend that uses RethinkDB as its data layer. At the time of writing this, Horizon is very early, although it is stable and I guess used in many apps already. It will probably change slightly in the future, but the main assumptions should stay the same.

It was released in May 2016.

So, what is it? You can think of it as an invisible backend configured and ready-to-use at the beginning of your project, so you don't need to do any additional work. Of course, you can if you want to. You can just grab some of its parts and integrate them with your server and backend.

All you need here to start with Horizon is Node 4.4 or higher and some additional steps. Let's see what it is. You need to:

  1. Install RethinkDB: http://rethinkdb.com/docs/install/
  2. Install Horizon from Npm: npm install -g horizon
  3. Initialize your custom app: hz init example_app
  4. Go to your app's folder: cd example_app
  5. Start the server: hz serve –dev

After initializing your app, you'll have a straightforward folder structure. You'll get two folders, src and dist, and that’s it. Your Horizon server will look for client files in the dist folder, but it won’t touch anything in there. So it is prepared to make usage of the src folder for your development files and the dist folder for your production. This is what we’ll do here in our demo app. It is a common usage of most development workflows. I like this simplicity and separation.

In this article, we'll use a standard Horizon dev server and its configuration. You can, of course, configure the server as you want. For our purposes, let's stick to the standard dev server. In this case, we don't need to do anything more with it. It just works. It will also create and run the RethinkDB instance for us.

You’re probably thinking about how my client files in the dist folder connect to the server and RethinkDB. This is done through a particular client-side library provided by Horizon. You’ll find it as a standard client JavaScript file which you can include in the index.html file. Of course, you can also use the Npm package, and this is what we’ll do in our TODO app.

Now, let's look at the Horizon's Collection API, which allows us to create collections and manage the data in the RethinkDB instance. I don't want to talk about the security stuff and permissions in this article. I'll try to write another one about it later, of course, in the context of our example app.

Horizon's Collection API

Horizon's documentation is quite good. It could be better, in my opinion, but for now, at this stage, it is good.

First of all, we need to know how to create our collection. You can do this on the client-side. We do this by:

const hz = new Horizon(); 
const messages = hz("messages");

So we've created a 'messages' collection here. Next, we want to know how to add and remove elements from the collection. You can do this by:

messages.store({ 
    id: 1, 
    from: 'Bob',
    text: "Hello from RethinkDB" 
});

messages.remove(1);
// or
messages.remove({id: 1});

The last important thing is how we will display our data. You can do this by using the Collection.fetch() method, which will return RxJS observable, so you’ll be able to subscribe to the data flow. Again, I don't want to go deep into the RxJS. For now, it isn't that important in the context of our Horizon's API. You just need to know that if you want to use Horizon, you probably need to study RxJS a little bit, which is worth it anyway.

Ok, let's get back to our API:

If you want to display your data, you can do something like:

messages.findAll({from: 'Bob'}).fetch().subscribe(msg => console.log(msg)); 

You'll get all messages from Bob in a collection at once. Then if there is a new message from Bob, you will get another console log with a collection of messages from Bob. Using RxJS here is a very nice feature because it could be used in apps based on React or Vue to replace whole data sets. So, for example, in a straightforward React app, you could do something like:

(...)
messages.findAll({from: 'Bob'}).fetch().subscribe(msg => this.setState({msg})); 
(...)

and then consume it like:

(...)
this.state.msg.map(message => <div>{message.text}</div>);
(...)

In our TODO app, we’ll use the tool designed for React and dramatically improve our workflow with Horizon and the data layer. So let's see what it is.

react-hz – like Redux, but in the Horizon world

So why did I write that react-hz is like Redux? Because it is very similar when used with React. I encourage you to read it. Of course, you can still use Redux in this stack if you want. For example, if you're going to manage your local state with it. I think it should work that way. Although I haven't tested it yet, I'm sure I'll do it at some point. For backend stuff and data sync, you can use react-hz, which has a very similar API.

How does it work? It wraps our React components and passes Horizon's subscriptions and mutation methods to our component's props. So as you can see, this is very similar to the React and Redux workflow.

All are reactive here. It is based on well-known data containers in the React world. You'll probably always want to separate your data container components from your UI components which will only consume the data. This tool is very simple and straightforward.

I like the fact that when you know how to use Redux in the React app, you'll be able to switch very quickly.

Also check out the react-hz readme file.

I only hope that this library will be maintained. If not, there will probably be some other similar options to choose from soon. (One example: horizon-react it allows you to use Redux and Horizon together).

Ok, let's jump into the example.

React/Webpack/Horizon TODO app example

You can treat this example app as a complete Horizon/React/Webpack boilerplate. I plan to use it when building future apps with Horizon and React.

You can see the code of the app here. There is a release 1.0.0 for Horizon 1.1.3 and also 2.0.0 for Horizon 2.0.0. I've done a standard Horizon project initialization, and then I copied my client-side React boilerplate into it. So now we have all development files in the src folder. We also have some other Webpack configuration files outside it and even client-side tests with Mocha and Enzyme. We'll take a closer look at this soon. For now, let’s clone the repository:

$ git clone https://github.com/juliancwirko/horizon-react-webpack-boilerplate.git

How does the workflow look? You need to start two servers, one of which is a Horizon standard server. You can run it by: hz serve —dev. The second one is the Webpack dev server which you can run in another terminal tab, of course from the same folder. You can run it by npm start. Before this, you need to install Npm dependencies if this is your first start. You can do this by npm install.

The Webpack server only serves client-side files, but in this case, it’s enough because it will connect with the Horizon backend separately. So we can develop our app, and when we are ready, we can build it (npm run build) to the dist folder. Then we will only need to run the Horizon server, which will serve static files from the dist folder.

So once again. When you develop your app, you should use two servers. The Horizon one and the client-side Webpack server. But when you built your client-side app, you can run only the Horizon server because it will grab your static files from the dist folder. For more details, read the README.md file in the repo.

Ok, let's get to the code.

In the root directory, you have some configuration files. So there are Webpack config files. Three files, one for the development environment, one for the test environment, and one for the production build. As you can see, we have here also ESLint configuration (Airbnb style guide) and Babel configuration so we can use all ES2015 goodies in the project. We have some folders like __test__, mocha-runner, .hz. The first two are just testing specs and configuration, and the third one is a configuration folder from Horizon. You can learn about it in the Horizon docs. And of course, we have the src and dist folders here. The most important is the src folder because we’ll build our front-end app in it. So, as mentioned before, after you clone the repo, you can run Horizon by hz serve --dev, and also in the other terminal tab, you can run the Webpack dev server by npm start. We can use Webpack's server, which will serve development files from the src folder. When you run npm run build, Webpack will bundle all necessary files in the dist folder, so from then on, you can just use Horizon's server.

Let's see how it’s configured in the 'src' folder.

You can open the src folder in the repo. The first most important file is App.js. Here we integrate our React app with the Horizon backend. We use react-hz here. So we have something like:

import { Horizon, HorizonProvider } from 'react-hz';

const horizonInstance = Horizon({ host: '127.0.0.1:8181' });

ReactDOM.render(
  <HorizonProvider instance={horizonInstance}>
    <Router routes={routes} history={browserHistory} />
  </HorizonProvider>
, document.getElementById('app'));

As you can see, we've initialized Horizon's instance here, and we use HorizonProvider, which will pass all Horizon's subscriptions and mutations down to all subcomponents.

We can then consume that in our components. So, for example, let's take a look at Todo.js component. As you can see, we use the connect() method from the react-hz package (Identical to the Redux connect method. Isn't it?). Thanks to react-hz, we can do something like:

(...)
export default connect(Todo, {
  mutations: {
    removeTodo: (hz) => (id) => hz('todos').remove(id),
    editTodo: (hz) => (id, todo, finished, timestamp) => hz('todos').replace({
      id,
      todo,
      finished,
      timestamp,
    }),
  },
});

We've just passed mutation methods to the props of our Todo component. So, for example, we can use the editTodo method in our edit event.

(...)
const handleEdit = () => {
    editTodo(id, todo, !finished, timestamp);
};
(...)

And in the component's jsx code:


(...)    
<button type="button" onClick={handleRemove}>
    <i className="fa fa-times"></i>
</button>
(...)

You pass the subscriptions the same way when you want to display the list of todo items. Let's look at the Home.js component when you can see the example. Here we have something like:

export default connect(Home, {
  subscriptions: {
    todos: (hz) => hz('todos').order('timestamp', 'descending').limit(10000),

  },
  mutations: {
    addTodo: (hz) => (todo, finished, timestamp) => hz('todos').store({
      todo, finished, timestamp,
    }),
  },
});

And in the jsx of the component, we’ll map through the todo collection.

(...)
<div>
    {todos.map((t, i) => <Todo
      id={t.id}
      todo={t.todo}
      finished={t.finished}
      timestamp={t.timestamp}
      key={i}
    />)}
</div>
(…) 

This is all that we have in the demo app in the context of Horizon. You could, of course, use much more. It depends on what you want to build with Horizon. As I said before, this is just an introduction and basic boilerplate, which you can find in the repository: horizon-react-webpack-boilerplate.

Let's sum it up

Horizon is ready to use the backend service without any additional configuration needed. Of course, you can configure it as you need. You can also embed it in your current backend stack. Horizon uses RethinkDB as its database, which I think (and hope) will be gaining popularity over time. It is an excellent alternative for monolithic frameworks like Meteor.

Here, we use a react boilerplate integrated with Horizon through the react-hz library, but of course, you can use something else if you want. This is just an example boilerplate.

I plan to write more about it when the new Horizon 2.0.0 version is battle-tested. There are many interesting topics like RethinkDB possibilities, data aggregations, Horizon's auth, oAuth etc., security, and whole section about permissions looks very promising. I'm looking forward to writing a real app using Horizon. I hope I find some time soon.

If you have suggestions or just want to comment on something, go ahead. You can find me on Twitter @theJulianIo.