Live State logolive-state

Quick Start

Install and run a minimal Live State server and client

1. Install

live-state is composed of a single package: @live-state/sync, but it's fully modular and tree-shakable, so you can keep only what you need.

Start off by installing the package:

npm install @live-state/sync

2. Define your schema

Let's start by defining the schema. This is a declarative model of the data you want to store. This example will have a single collection: tasks.

schema.ts
import { createSchema, object, id, string, number } from "@live-state/sync";

const tasks = object("tasks", {
  id: id(),
  title: string(),
  priority: number().default(0),
});

export const schema = createSchema({ tasks });

3. Create a router

Now let's create a router. This will be a simple router, serving only a collection route for the tasks collection.

router.ts
import { routeFactory, router } from "@live-state/sync/server";
import { schema } from "./schema";

export const appRouter = router({
  schema,
  routes: {
    tasks: routeFactory().collectionRoute(schema.tasks),
  },
});

export type Router = typeof appRouter; // Export the router type to use later in the client

4. Create a server

Now that we have a router and schema, we can serve it. This example will use PostgreSQL for persistence and Express adapter.

server.ts
import express from "express";
import expressWs from "express-ws";
import { Pool } from "pg";
import cors from "cors";
import { server, SQLStorage, routeFactory } from "@live-state/sync/server";

import { schema } from "./schema";
import { appRouter } from "./router";

const lsServer = server({
  router: appRouter,
  storage: new SQLStorage(
    new Pool({
      connectionString: "postgresql://admin:admin@localhost:5442/live-state",
    })
  ),
  schema,
});

const { app } = expressWs(express());

app
  .use(express.urlencoded({ extended: true }))
  .use(express.json())
  .use(cors());

expressAdapter(app, lsServer);

app.listen(5001, () => console.log("api running on 5001"));

4. Create a client

Last thing we need to do is create a client. For this example, we'll use the WebSocket client.

client.ts
import {
  ,
  ,
  ,
} from "@live-state/sync/client";
import {  } from "./schema";
import type {  } from "./router"; // Import only the router type, otherwise you will get build errors

export const { ,  } = <>({
  : "ws://localhost:5001/ws",
  ,
  : false,
});

...({
  : "task_1",
  : "First task",
});

const tasks = ...();
const tasks: {
    id: string;
    title: string;
    priority: number;
}[]

That's it, now you can build UI and it will stay live-synced via WebSocket, with optimistic updates and conflict resolution built in. And as you can see, completely type-safe.