Offline-first for every application

 

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

 

Benefits

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

Realtime, built-in. Want realtime UI updates? Call replicache.subscribe() and wire that to your UI. That's it. Whenever the underlying data changes, due to either local or server-side changes, the subscription will fire and the view will refresh.

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.

How it Works

Overview

Replicache is an on-device cache with a server-side replica.

Changes are allowed at either replica. Changes made on the server side are pushed to the client when connectivity allows, and vice versa.

Replicache provides a programming model that makes it easy to reason about and resolve conflicting changes, and ensures that device state always ends up consistent with server state.

①  Read Data

let sub = replicache.subscribe(
  async (tx) => (await tx.scan("/todo/")).filter(
    (item) => item.text.includes("Dog"))
);
sub.onchange = () =>
  this.setState({
    todos: sub.result,
  });                 
@ObservedObject let sub = replicache.subscribe {
  $0.scan("/todo/").filter { $0.text.contains("Dog") }
}
var body: some View {
  List {
    ForEach(sub.results()) { result in 
      TodoRow(result)
    }
  }  
}
final Subscription sub = replicache.subscribe(tx -> {
  return tx.scan("/todo/").filter(
    item -> item.text().contains("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);
final sub = rep.subscribe(
  (tx) async => (await tx.scan(prefix: "/todo/"))
    .where((item) => item.text.contains("Dog")));

sub.listen((result) {
  setState(() {
    _result = result;
  });
});

Read directly from the local replica with zero network delays using query().

Easily build realtime applications using subscribe(). Whenever the underlying data changes — either due to remote or local changes — the affected views refresh automatically and consistently.

②  Write Data

const createTodo = replicache.register("/create-todo",
  async (tx, args) => {
    const {id, text, complete} = args;
    await tx.put(`/todo/${id}`, {text, complete});
  });
createTodo({id: uuid(), text: this.state.todoText, complete: false});                    
let createTodo = replicache.register("/create-todo",
    { (tx: WriteTransaction, args: Any) -> Any {
  tx.put(#"/todo/\#(args["id"])"#, args);
});
createTodo(["id": UUID().uuidString, "text": todoText, "done": false]);
Mutator mutator = replicache.register("/create-todo",
    (WriteTransaction tx, JSONValue args) -> {
  tx.put(String.format("/todo/%s", args.get("id")), args);
});
mutator("/create-todo", JSON.createObjectBuilder()
  .add("id", id.string())
  .add("text", todoText)
  .add("done", done).build());
const createTodo = replicache.register("/create-todo",
  (tx, args) async {
    await tx.put("/todo/${args["id"]}", args);
  });

createTodo({"id": uuid(), "text": _todoText, "complete": false}); 

To make changes on the client, register a mutator.

Replicache executes the mutator immediately against the local cache. Subscriptions re-fire, and views are instantly updated with the pending change.

③  Upstream Sync

POST /replicache-batch HTTP/2
Host: myservice.com

{
  "clientID": "CB94867E-94B7-48F3-A3C1-287871E1F7FD",
  "mutations": [
    {
      "id": 7,
      "name": "createTodo",
      "args": {
        "id": "AE2E880D-C4BD-473A-B5E0-29A4A9965EE9",
        "title": "Fix the car",
        "complete": false
      }
    },
    {
      "id": 8,
      "name": "toggleComplete",
      "args": {
        "id": "5C2F21E8-A9CC-4DA8-91D6-97D2D1F7CECF",
        "done": true
      }
    }
  ]
}
HTTP/2 200 
Content-Type: application/json

[
  {
    "id": 8,
    "error": "specified todo not found"
  }
]

Batches of pending write transactions are sent to the /replicache-batch endpoint on your service as connectivity allows. These requests are delayed, but otherwise normal. Your service defensively checks for conflicts, and ignores, modifies, or rejects the request as normal.

④, ⑤  Downstream

POST /replicache-device-view HTTP/2
Host: myservice.com
Authorization: Bearer 51857000befac83d338df7661d00d81011d7fb10

HTTP/2 200
Content-Type: application/json

{
  "lastMutationID": 6,
  "/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
  }
}

The server replica requests the latest offline state from your service's /replicache-client-state endpoint. Just return all data that should be in a user's cache (up to 20MB of JSON) every time. Replicache computes a minimal diff and pushes it to the device.

The Repicache client rewinds the cache to the point before sync started, integrates the new changes, and then replays any newer pending changes on top.

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.

Get Started

Replicache is now available in alpha for Flutter-based mobile applications. Expirement, let us know what you build. If you love it as much as we do, you'll be able to roll it out in production later this year.

Get Started (Flutter)
Or join our mailing list to be notified of future releases.