Nats (Message Bus / PubSub)

NATS is a Message Bus which can optionally be enabled in Iterapp. It makes communicating between your pods easier. More importantly it supports pub/sub, which means that one of your pods can broadcast a message to all of your other pods. This is essential for real-time-updates, like for collaboration or chat. For instance this is what makes collaborative editing possible in Icecalc, and powers the chat in Anywhere.

When enabled, your app get its own nats-account for each of its environments. Since a nats-account also functions as a namespace, apps cannot communicate with each other, and cannot communicate between environments.

This means that when for instance broadcast anything to a subject called test, only subscribers in pods for the same app and same environment will receive the broadcast.

Nats is at most once delivery. That is, it gives no guarantees of message delivery. In most cases the message will be delivered, but for instance if your pod was reconnecting to NATS the second the package was sent, it will not receive it. If you need to have guarantees, you can do that using a number of ways. For instance sending acks when the package was received, or using sequence numbers to discover missed packages.

In the future, we might want to add JetStream to the NATS-cluster in Iterapp. JetStream will give at least once delivery. However right now that technology is a bit too immature.

Tip

For a basic example of a iterapp-app with nats, take a look at iterate/example-nats

Enabling

Add the following to your iterapp.toml

[nats]

When deploying your app, an account will be created for you, and the app will get the environment-variables needed to connect.

Environment Variables

These are the env-variables your app will receive.

NameDescription
NATS_URLURL used to connect to the nats-server
NATS_CREDENTIALSThe credentials required to connect to the nats-server
NATS_CREDENTIALS_FILEPath of a file with the same content as in NATS_CREDENTIALS
NATS_CA_FILEPath to a file with the CA-certificate used to sign the TLS-certificate used by NATS

Note

NATS is using a self-signed certificate, so you'll need to add the CA to the certificate store when connection, if not you'll get an error.

Running locally

When developing an application that uses NATS, you should have something runninc locally that NATS can connect to. You don't need to configure anything, just start a local nats-instance.

Using docker

> docker run -p 4222:4222 -ti nats:latest

Using homebrew

> brew install nats-server
> nats-server

Downloading a release

Download the latest release from the release-page for NATS.

Run it without any configuration

nats-server

Build from source

> GO111MODULE=on go get github.com/nats-io/nats-server/v2
> nats-server

Connecting to NATS

Here is examples for how to connect to the terapp NATS server in different languages

NATS has clients in most languages, and it should be relatively straightforward to take the ideas from here and write in other languages, but please if you do, update this documentation.

NODE.js (Javascript)

Note that we only use credentials if they are defined. This means that when developing locally you can connect to a local nats server that does not require authentication, nor SSL, which is the default nats-server config.

(If someone converts this to typescript, please add the example here :) )

const { connect, StringCodec, credsAuthenticator } = require('nats');

let authenticator;
// NATS_CREDENTIALS is undefined in development, but defined in production
if (process.env.NATS_CREDENTIALS) {
  authenticator = credsAuthenticator(
    new TextEncoder().encode(process.env.NATS_CREDENTIALS)
  );
}

const nc = await connect({
  servers: process.env.NATS_URL,
  // NATS_CA_FILE is undefined in development, but defined in production
  tls: process.env.NATS_CA_FILE && { caFile: process.env.NATS_CA_FILE },
  authenticator,
});

Rust

Note that we only use credentials if they are defined. This means that when developing locally you can connect to a local nats server that does not require authentication, nor SSL, which is the default nats-server config.

This uses the async nats, it should be relatively straightforward to convert to using the sync nats packages, since the API is similar.

#![allow(unused)]
fn main() {
// We use anyhow for easier error-management
use anyhow::Result;
use async_nats::Connection;


pub async fn create_connection() -> Connection {
    loop {
        match try_create_connection().await {
            Ok(conn) => return conn,
            Err(err) => {
                println!("Error connecting to nats: {}. Retrying...", err);
                tokio::time::sleep(Duration::from_secs(2)).await;
            }
        }
    }
}

pub async fn try_create_connection() -> Result<Connection> {
    let nats_system_account_cred_file = std::env::var("NATS_CREDENTIALS_FILE").ok();
    let nats_ca_file = std::env::var("NATS_CA_FILE").ok();
    let nats_url = env_or_die("NATS_URL")?;

    let mut options = match nats_system_account_cred_file {
        Some(cred_file) => async_nats::Options::with_credentials(&cred_file),
        None => async_nats::Options::new(),
    };

    if let Some(ca_root) = nats_ca_file {
        options = options.add_root_certificate(&ca_root);
    }

    let nc = options.max_reconnects(None).connect(&nats_url).await?;

    Ok(nc)
}
}