Offline-first for every application

 

Replicache is the best way to build blazingly responsive offline-first apps.

 

Benefits

Instant User Actions. By buffering all reads and writes to a persistent local cache, apps built with Replicache respond immediately to user actions — online, offline, or anything in between.

Works with any backend. You have an existing service, and an existing client. Keep those! Replicache sits between them and makes it easy to add offline-first goodness to your existing app.

Dramatically easier conflict resolution. Merging the effects of conflicting histories is super hard. Instead, Replicache rewinds offline operations and replays them when you come back online. Sort of like git rebase for your application state. It's the easiest way to deal with conflicts that is...

Correct. Replicache is designed to provide Causal+ Consistency, which is one of the strongest consistency models possible in an offline-first system. Our design has been reviewed by Kyle Kingsbury of Jepsen.

Your service is authoritative. Whatever it decides is Truth. All clients are guaranteed to snap into alignment on next sync.

Delta sync, built-in: Replicache automatically computes a minimal delta to push to clients for every sync.

Automatic subscriptions: Want live UI updates? Call replicache.subscribe(). That's it. Whenever the underlying data changes, for any reason, the subscription will fire with the new result. Unlike other solutions, you don't need to manually fixup queries when modifying data offline.

How it Works

Overview

Replicache is a per-user cache that sits between your backend and client.

Whatever you put in the server replica gets sent as deltas to the client on next sync. Any changes you make on the client replica get forwarded as requests to your service's APIs.

Replicache guarantees that after each sync, a client replica will exactly match the server.

The OfflineView API

// Put inside `pages/api/offline-view.js`
export default (req, res) => {
  res.json({
    "/todo/39B224C1-1DBE-48D2-B89D-1DC4BA9821AA": {
      text: "Take out the garbage",
      done: false,
    },
    "/todo/77173E44-C620-429C-9CD1-16548D58A94A": {
      text: "Feed the dog",
      done: true,
    },
  });
};
app.post("/offline-view", () {
  return {
    "/todo/39B224C1-1DBE-48D2-B89D-1DC4BA9821AA": {
      text: "Take out the garbage",
      done: false,
    },
    "/todo/77173E44-C620-429C-9CD1-16548D58A94A": {
      text: "Feed the dog",
      done: true,
    },
  };
});
http.HandleFunc("/offline-state", func(w http.ResponseWriter, r *http.Request) {
  err := json.NewMarshaler(r.Body).Marshal(map[string]Todo{
    "/todo/39B224C1-1DBE-48D2-B89D-1DC4BA9821AA": Todo{
      Text: "Take out the garbage",
      Done: false,
    },
    "/todo/77173E44-C620-429C-9CD1-16548D58A94A": Todo{
      Text: "Feed the dog",
      Done: true,
    },
  })
  if err != nil {
    log.Fatalf(err.Error())
  }
})

On your server, implement an OfflineView API, which returns the entire offline state (up to 20MB of JSON) for each user.

Just return the entire view every time. The Replicache server will call this API periodically and automatically compute fine-grained deltas to send to clients.

The Replicache JavaScript Bundle

function searchTodos(substring) {
  return replicache.query("/todo/").filter(
      entry => entry.text.indexOf(substring) > -1);
}

function createTodo(id, text, done) {
  replicache.put("/todo/" + id, {text,done});
}

The Replicache bundle contains functions (written in ES6) you will use to access Replicache from each client. Think of them as your offline stored procedures.

Bundle functions run in an isolated environment that Replicache controls. This allows Replicache to re-run these functions when necessary to refresh subscriptions, or rewind and replay history.

Reading Data

let sub = replicache.subscribe("searchTodos", "dog");
sub.onchange = () {
  setState({
    todos: sub.result,
  });
};
@ObservedObject let sub = replicache.subscribe("searchTodos", "dog");
var body: some View {
  List {
    ForEach(sub.results()) { result in 
      TodoRow(result)
    }
  }  
}
final Subscription sub = replicache.subscribe("searchTodos", "dog");
  
final Observer<JSONObject> observer = new Observer<JSONObject>() {
  @Override
  public void onChanged(@Nullable final JSONObject result) {
    // Update the UI, in this case, a table.
    tableView.setData(result);
  }
};

sub.observe(this, observer);
const sub = replicache.subscribe("searchTodos", ["dog"]);
await for (var todos in stream) {
  setState(() {
    todos: sub.result,
  });
}
        

Build your views by executing read queries from your bundle.

Whenever the underlying data changes, affected subscriptions automatically refresh.

Writing Data

function handleButtonClick() {
  var id = uuid();
  replicache.exec({
    local: {name: 'createTodo', args: [id, this.state.todoText, false]},
    remote: {path: '/createTodo', payload: {id: id, text: this.state.todoText, done: false}},
  });
};
func handleButtonClick() {
  let id = uuid();
  replicache.exec(
    replicache.LocalWrite('createTodo', [id, todoText, false]),
    replicache.RemoteWrite(/createtodo', ["id": id, "text": todoText, done: false]));
};
void handleButtonClick() {
  UUID id = UUID.randomUUID();
  replicache.exec(
    replicache.LocalWrite('createTodo', args: new Object[]{id.string(), todoText, done}),
    replicache.RemoteWrite('/createTodo', JSON.createObjectBuilder()
      .add("id", id.string())
      .add("text", todoText)
      .add("done", done)).build());
void handleButtonClick() {
  const id = uuid();
  replicache.exec(
    replicache.LocalWrite('createTodo', [id, this.state.todoText, false]),
    replicache.RemoteWrite('/createTodo', {id: uuid(), text: this.state.todoText, done: false}));
};

To make changes, specify both a bundle function and an HTTP request on your server.

Replicache executes the bundle function immediately, and the HTTP transaction is queued and retried periodically until it succeeds. Once the remote transaction completes, Replicache rewinds the cache to the point before the local change, pulls the latest server state, then replays any remaining local history.

Who's Behind This?

Aaron Boodman

Hi, I'm Aaron Boodman (I'm the big one).

I've been working on sync on and off for over fifteen years, including major projects at Google and my last startup.

I pushed eject on Silicon Valley last year and moved my family out to Oahu to try and better balance life, work, and family.

Fritz Schneider

I'm Fritz Schneider.

In previous lives I worked at Google (twice), and on various startups, including the last one with Aaron.

I live on Maui, a few rocks over from Oahu, which makes us, officially, a  ✨distributed company✨.

Erik Arvidsson

👋 Erik Arvidsson here.

I've worked at Google (Chrome, Blink, V8, Gmail), then Attic Labs with Aaron and Fritz, and most recenly at Quip.

I live in San Francisco with wife, kids and cats.

We're building a small, sustainable software partnership. No VC financing, no rocket ship growth expectations. Just high-quality software, sold at a fair price.

Curiosity Piqued?

Request early access, and we'll work closely with you to get Replicache working in development. If you love it as much as we do, you'll be able to roll it out in production later this year.