Getting started with Horizon

If you haven’t installed Horizon, do so now. (Read the Installation instructions for more details.)

npm install -g horizon

Using the Horizon CLI

Interactions with Horizon are performed with the hz application. hz has a number of commands, of which we are going to use the following two:

Initialize an example application

Let’s create a new Horizon application. Go to a directory you’d like to install this application into and type:

hz init example_app

This will create the example_app directory and install a few files into it. (If you run hz init without giving it a directory, it will install these files into the current directory.)

$ tree example_app
example_app/
├── .hz
│   ├── config.toml
│   ├── schema.toml
│   └── secrets.toml
├── dist
│   └── index.html
└── src

Here’s what these files and directories are:

Note: In Horizon 1.x, authentication options were stored in config.toml.

Start the server

Start Horizon to test it out:

hz serve --dev

You’ll see a series of output messages as Horizon starts a RethinkDB server, ending with Metadata synced with server, ready for queries. Now, go to http://localhost:8181. You should see the message “App works!” scrolling across your screen.

Here’s what hz serve actually does:

Passing the --dev flag to hz serve puts it in development mode, which makes the following changes. (All of these can also be set individually with separate flags to serve.)

You can find the complete list of command line flags for hz serve in the documentation for the Horizon CLI.

In production (i.e., without the --dev flag), you’ll use the .hz/config.toml file to set these and other options. See Configuring Horizon for details.

Talk to Horizon

Load the index.html file in example_app. It’s pretty short:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <script src="/horizon/horizon.js"></script>
    <script>
      var horizon = Horizon();
      horizon.onReady(function() {
        document.querySelector('h1').innerHTML = 'App works!'
      });
      horizon.connect();
    </script>
  </head>
  <body>
   <marquee><h1></h1></marquee>
  </body>
</html>

The two script tags do the work here. The first loads the actual Horizon client library, horizon.js; the second is a (very tiny) Horizon application:

Horizon Collections

The heart of Horizon is the Collection object, which lets you store, retrieve, and filter documents. Many Collection methods for reading and writing documents return RxJS Observables.

// Create a "messages" collection
const chat = horizon("messages");

To store documents in the collection, use store.

let message = {
  text: "What a beautiful horizon!",
  datetime: new Date(),
  author: "@dalanmiller"
}

chat.store(message);

To retrieve documents, use fetch.

chat.fetch().subscribe(
  (items) => {
    items.forEach((item) => {
      // Each result from the chat collection
      //  will pass through this function
      console.log(item);
    })
  },
  // If an error occurs, this function
  //  will execute with the `err` message
  (err) => {
    console.log(err);
  })

We use the RxJS subscribe method to receive items from the collection, as well as to provide an error handler.

Removing documents

To remove documents from a collection, use either remove or removeAll.

// These two queries are equivalent and will remove the document with id: 1
chat.remove(1).subscribe((id) => { console.log(id) })
chat.remove({id: 1}).subscribe((id) => {console.log(id)})

// Will remove documents with ids 1, 2, and 3 from the collection
chat.removeAll([1, 2, 3])

As with the other functions, you can chain subscribe onto the remove functions to provide response and error handlers.

Watching for changes

We can “listen” to an entire collection, query, or a single document by using watch. This lets us build apps that update state immediately as data changes in the database.

// Watch all documents. If any of them change, call the handler function.
chat.watch().subscribe((docs) => { console.log(docs)  })

// Query all documents and sort them in ascending order by datetime,
//  then if any of them change, the handler function is called.
chat.order("datetime").watch().subscribe((docs) => { console.log(docs)  })

// Watch a single document in the collection.
chat.find({author: "@dalanmiller"}).watch().subscribe((doc) => { console.log(doc) })

By default, the Observable returned from watch receives the entire collection of documents when a single one changes. This makes it easy to use frameworks such as Vue or React, allowing you to simply replace your existing copy of the collection with the new array returned by Horizon.

let chats = [];

// Query chats with `.order`, which by default is in ascending order
chat.order("datetime").watch().subscribe(

  // Returns the entire array
  (newChats) => {

    // Here we replace the old value of `chats` with the new
    //  array. Frameworks such as React will re-render based
    //  on the new values inserted into the array.
    chats = newChats;
  },

  (err) => {
    console.log(err);
  })

To learn more about how Horizon works with React, check out the complete Horizon & React example.

Putting it all together

Now that we have the basics covered, let’s pretend we’re building a simple chat application where the messages are displayed in ascending order. We now have to get a little opinionated and choose a front-end framework. For the purposes of this quick demo, we’ll use Vue.js.

The first step is to initialize our Horizon app directory:

$ hz init chat-app
Created new project directory chat-app
Created chat-app/src directory
Created chat-app/dist directory
Created chat-app/dist/index.html example
Created chat-app/.hz directory
Created chat-app/.hz/config.toml

Now, we’ll cd into the chat-app directory and start putting files into dist, the automatically-created static file directory. All files in here will be served by default in Horizon.

Let’s start first with app.js. The key parts here are listening to the stream of chat messages with a .watch() query, as well as being able to input a new message with the .store() query. Here’s what your app.js should look like:

{% raw %}

const horizon = new Horizon()
const messages = horizon('messages')

const app = new Vue({
  el: '#app',
  template: `
    <div>
      <div id="chatMessages">
        <ul>
          <li v-for="message in messages">
            {{ message.text }}
          </li>
        </ul>
      </div>
      <div id="input">
        <input @keyup.enter="sendMessage" ></input>
      </div>
    </div>
  `,
  data: {
    // Our dynamic list of chat messages
    messages: []
  },
  created() {
    // Subscribe to messages
    messages.order('datetime', 'descending').limit(10).watch()
    .subscribe(allMessages => {
        // Make a copy of the array and reverse it, so newest images push into
        // the messages feed from the bottom of the rendered list. (Otherwise
        // they appear initially at the top and move down)
        this.messages = [...allMessages].reverse()
      },
      // When error occurs on server
      error => console.log(error)
    )

    // Triggers when client successfully connects to server
    horizon.onReady().subscribe(
      () => console.log("Connected to Horizon server")
    )

    // Triggers when disconnected from server
    horizon.onDisconnected().subscribe(
      () => console.log("Disconnected from Horizon server")
    )
  },
  methods: {
    sendMessage(event) {
      messages.store({
        text: event.target.value, // Current value inside <input> tag
        datetime: new Date() // Warning clock skew!
      }).subscribe(
          // Returns id of saved objects
          result => console.log(result),
          // Returns server error message
          error => console.log(error)
        )
        // Clear input for next message
        event.target.value = ''
    }
  }
})

{% endraw %}

Now, we’ll need to replace the boilerplate in the index.html file with code that loads Horizon, Vue.js, and our application.

<!DOCTYPE html>
<html>
  <head>
    <script src="/horizon/horizon.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.25/vue.js"></script>
    <script type="text/javascript" src="/app.js"></script>
  </body>
</html>

So your app directory should look like this:

chat-app
├── .hz
│   └── config.toml
├── dist
│   ├── app.js
│   ├── index.html
└── src

You can now run hz serve --dev and load your working chat application at http://localhost:8181. Open it in multiple browser tabs to watch the application update instantly!

From here, you could take any framework and add these functions to create a realtime chat application without writing a single line of backend code!

Integrating Horizon with an existing application

While Horizon serves static files from the dist folder by default, you can use any method to serve your files. You have two options to include the Horizon client library:

We recommend the first option, as that will prevent any possibly mismatches between the client library version and the Horizon server. In your application, you’ll need to include the Horizon client file, and specify the Horizon port number when initializing the connection.

<script src="/horizon/horizon.js"></script>

<script>
// Specify the host property for initializing the Horizon connection
const horizon = Horizon({host: 'localhost:8181'});
</script>

However, if you’re using Webpack or a similar build setup, or requesting the .js library at load time isn’t desirable, just add the client library as an NPM dependency (npm install @horizon/client).

import Horizon from '@horizon/client';

// Specify the host property for initializing the Horizon connection
const horizon = Horizon({host: 'localhost:8181'});

Further reading