# Jazz (react)
## Getting started
### Overview
# Learn some Jazz
**Jazz is a new kind of database** that's **distributed** across your frontend, containers, serverless functions and its own storage cloud.
It syncs structured data, files and LLM streams instantly, and looks like local reactive JSON state.
It also provides auth, orgs & teams, real-time multiplayer, edit histories, permissions, E2E encryption and offline-support out of the box.
---
## Quickstart
### Show me
[Check out our tiny To Do list example](/docs#a-minimal-jazz-app) to see what Jazz can do in a nutshell.
### Help me understand
Follow our [quickstart guide](/docs/quickstart) for a more detailed guide on building a simple app with Jazz.
### Just want to get started?
You can use [create-jazz-app](/docs/tooling-and-resources/create-jazz-app) to create a new Jazz project from one of our starter templates or example apps:
```sh
npx create-jazz-app@latest --api-key you@example.com
```
**Using an LLM?** [Add our llms.txt](/react/llms-full.txt) to your context window!
**Using an LLM?** Set up the [Jazz MCP server](/docs/tooling-and-resources/mcp-server) to give your AI assistant live, searchable access to the Jazz docs.
**Info:**
Requires at least Node.js v20\. See our [Troubleshooting Guide](/docs/troubleshooting) for quick fixes.
## How it works
1. **Define your data** with CoValues schemas
2. **Connect to storage infrastructure** (Jazz Cloud or self-hosted)
3. **Create and edit CoValues** locally
4. **Get automatic sync and persistence** across all devices and users
Your UI updates instantly on every change, everywhere. It's like having reactive local state that happens to be shared with the world.
## A Minimal Jazz App
Here, we'll scratch the surface of what you can do with Jazz. We'll build a quick and easy To Do list app — easy to use, easy to build, and easy to make comparisons with!
This is the end result: we're showing it here running in two iframes, updating in real-time through the Jazz Cloud.
Try adding items on the left and watch them appear instantly on the right!
**Info: Using Jazz Cloud**
These two iframes are syncing through the Jazz Cloud. You can use the toggle in the top right to switch between 'online' and 'offline' on each client, and see how with Jazz, you can keep working even when you're offline.
### Imports
Start by importing Jazz into your app.
```ts
import { co, z } from 'jazz-tools';
import { JazzBrowserContextManager } from 'jazz-tools/browser';
```
### Schema
Then, define what your data looks like using [Collaborative Values](/docs/core-concepts/covalues/overview) — the building blocks that make Jazz apps work.
```ts
const ToDo = co.map({ title: z.string(), completed: z.boolean() });
const ToDoList = co.list(ToDo);
```
### Context
Next, [give your app some context](/docs/project-setup#give-your-app-context) and tell Jazz your sync strategy — use the Jazz Cloud to get started quickly. We'll also create our to do list and get its ID here to use later.
```ts
await new JazzBrowserContextManager().createContext({
sync: {
peer: 'wss://cloud.jazz.tools?key=minimal-vanilla-example',
when: 'always',
},
});
const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]);
const listId = newList.$jazz.id;
```
### Build your UI
Now, build a basic UI skeleton for your app.
```ts
const app = document.querySelector('#app')!;
const id = Object.assign(document.createElement('small'), {
innerText: `List ID: ${listId}`,
});
const listContainer = document.createElement('div');
app.append(listContainer, id);
```
### Display Items
Display your items and add logic to mark them as done...
```ts
function toDoItemElement(todo: co.loaded) {
const label = document.createElement('label');
const checkbox = Object.assign(document.createElement('input'), {
type: 'checkbox',
checked: todo.completed,
onclick: () => todo.$jazz.set('completed', checkbox.checked),
});
label.append(checkbox, todo.title);
return label;
}
```
### Add New Items
...and add new items to the list using an input and a button.
```ts
function newToDoFormElement(list: co.loaded) {
const form = Object.assign(document.createElement('form'), {
onsubmit: (e: Event) => {
e.preventDefault();
list.$jazz.push({ title: input.value, completed: false });
}
});
const input = Object.assign(document.createElement('input'), {
placeholder: 'New task',
});
const btn = Object.assign(document.createElement('button'), {
innerText: 'Add',
});
form.append(input, btn);
return form;
}
```
### Subscribe to Changes
Now for the magic: listen to changes coming from [**anyone, anywhere**](/docs/permissions-and-sharing/overview), and update your UI in real time.
```ts
const unsubscribe = ToDoList.subscribe(
listId,
{ resolve: { $each: true } },
(toDoList) => {
const addForm = newToDoFormElement(toDoList);
listContainer.replaceChildren(
...toDoList.map((todo) => {
return toDoItemElement(todo);
}),
addForm
);
}
);
```
### Simple Routing
Lastly, we'll add a tiny bit of routing logic to be able to share the list by URL: if there's an `id` search parameter, that'll be the list we'll subscribe to later. If we don't have an `id`, we'll [create a new ToDo list](/docs/core-concepts/covalues/colists#creating-colists). We'll replace the section where we created the `ToDoList` above.
```ts
//[!code --:2]
const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]);
const listId = newList.$jazz.id;
// [!code ++:8]
const listId = new URLSearchParams(window.location.search).get('id');
if (!listId) {
const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]);
await newList.$jazz.waitForSync();
window.location.search = `?id=${newList.$jazz.id}`;
throw new Error('Redirecting...');
}
```
### All Together
Put it all together for a simple Jazz app in less than 100 lines of code.
```ts
import { co, z } from 'jazz-tools';
import { JazzBrowserContextManager } from 'jazz-tools/browser';
const ToDo = co.map({ title: z.string(), completed: z.boolean() });
const ToDoList = co.list(ToDo);
await new JazzBrowserContextManager().createContext({
sync: {
peer: 'wss://cloud.jazz.tools?key=minimal-vanilla-example',
when: 'always',
},
});
const listId = new URLSearchParams(window.location.search).get('id');
if (!listId) {
const newList = ToDoList.create([{ title: 'Learn Jazz', completed: false }]);
await newList.$jazz.waitForSync();
window.location.search = `?id=${newList.$jazz.id}`;
throw new Error('Redirecting...');
}
const app = document.querySelector('#app')!;
const id = Object.assign(document.createElement('small'), {
innerText: `List ID: ${listId}`,
});
const listContainer = document.createElement('div');
app.append(listContainer, id);
function toDoItemElement(todo: co.loaded) {
const label = document.createElement('label');
const checkbox = Object.assign(document.createElement('input'), {
type: 'checkbox',
checked: todo.completed,
onclick: () => todo.$jazz.set('completed', checkbox.checked),
});
label.append(checkbox, todo.title);
return label;
}
function newToDoFormElement(list: co.loaded) {
const form = Object.assign(document.createElement('form'), {
onsubmit: (e: Event) => {
e.preventDefault();
list.$jazz.push({ title: input.value, completed: false });
}
});
const input = Object.assign(document.createElement('input'), {
placeholder: 'New task',
});
const btn = Object.assign(document.createElement('button'), {
innerText: 'Add',
});
form.append(input, btn);
return form;
}
const unsubscribe = ToDoList.subscribe(
listId,
{ resolve: { $each: true } },
(toDoList) => {
const addForm = newToDoFormElement(toDoList);
listContainer.replaceChildren(
...toDoList.map((todo) => {
return toDoItemElement(todo);
}),
addForm
);
}
);
```
## Want to see more?
Have a look at our [example apps](/examples) for inspiration and to see what's possible with Jazz. From real-time chat and collaborative editors to file sharing and social features — these are just the beginning of what you can build.
If you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42). We'd love to help you get started.
### Quickstart
# Get started with Jazz in 10 minutes
This quickstart guide will take you from an empty project to a working app with a simple data model and components to create and display your data.
## Create your App
We'll be using Next.js for this guide per the [React team's recommendation](https://react.dev/learn/creating-a-react-app), but Jazz works great with vanilla React and other full-stack frameworks too.
You can accept the defaults for all the questions, or customise the project as you like.
```sh
npx create-next-app@latest --typescript jazzfest
cd jazzfest
```
**Note: Requires Node.js 20+**
## Install Jazz
The `jazz-tools` package includes everything you're going to need to build your first Jazz app.
```sh
npm install jazz-tools
```
## Get your free API key
Sign up for a free API key at [dashboard.jazz.tools](https://dashboard.jazz.tools) for higher limits or production use, or use your email address as a temporary key to get started quickly.
**File name: .env**
```bash
NEXT_PUBLIC_JAZZ_API_KEY="you@example.com" # or your API key
```
## Define your schema
Jazz uses Zod for more simple data types (like strings, numbers, booleans), and its own schemas to create collaborative data structures known as CoValues. CoValues are automatically persisted across your devices and the cloud and synced in real-time. Here we're defining a schema made up of both Zod types and CoValues.
Adding a `root` to the user's account gives us a container that can be used to keep a track of all the data a user might need to use the app.
The migration runs when the user logs in, and ensures the account is properly set up before we try to use it.
**File name: app/schema.ts**
```ts
import { co, z } from "jazz-tools";
export const Band = co.map({
name: z.string(), // Zod primitive type
});
export const Festival = co.list(Band);
export const JazzFestAccountRoot = co.map({
myFestival: Festival,
});
export const JazzFestAccount = co
.account({
root: JazzFestAccountRoot,
profile: co.profile(),
})
.withMigration((account) => {
if (!account.$jazz.has("root")) {
account.$jazz.set("root", {
myFestival: [],
});
}
});
```
## Add the Jazz Provider \[!framework=react,svelte\]
Wrap your app with a provider so components can use Jazz.
**File name: app/components/JazzWrapper.tsx**
```tsx
"use client"; // tells Next.js that this component can't be server-side rendered. If you're not using Next.js, you can remove it.
import { JazzReactProvider } from "jazz-tools/react";
import { JazzFestAccount } from "@/app/schema";
const apiKey = process.env.NEXT_PUBLIC_JAZZ_API_KEY;
export function JazzWrapper({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
**File name: app/layout.tsx**
```tsx
import { JazzWrapper } from "@/app/components/JazzWrapper";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
## Start your app
Moment of truth — time to start your app and see if it works.
```bash
npm run dev
```
If everything's going according to plan, you should see the default Next.js welcome page!
### Not loading?
If you're not seeing the welcome page:
* Check you wrapped your app with the Jazz Provider in `app/layout.tsx`
* Check your schema is properly defined in `app/schema.ts`
**Info: Still stuck?** Ask for help on [Discord](https://discord.gg/utDMjHYg42)!
## Create data
Let's create a simple form to add a new band to the festival. We'll use the `useAccount` hook to get the current account and tell Jazz to load the `myFestival` CoValue by passing a `resolve` query.
**File name: app/components/NewBand.tsx**
```tsx
"use client";
import { useAccount } from "jazz-tools/react";
import { JazzFestAccount } from "@/app/schema";
import { useState } from "react";
export function NewBand() {
const me = useAccount(JazzFestAccount, {
resolve: { root: { myFestival: true } },
});
const [name, setName] = useState("");
const handleSave = () => {
if (!me.$isLoaded) return;
me.root.myFestival.$jazz.push({ name });
setName("");
};
return (
setName(e.target.value)}
/>
);
}
```
## Display your data
Now we've got a way to create data, so let's add a component to display it.
**File name: app/components/Festival.tsx**
```tsx
"use client";
import { useAccount } from "jazz-tools/react";
import { JazzFestAccount } from "@/app/schema";
export function Festival() {
const me = useAccount(JazzFestAccount, {
resolve: {
root: {
myFestival: {
$each: true
}
}
},
});
if (!me.$isLoaded) return null;
return (
{me.root.myFestival.map(
(band) => band &&
{band.name}
,
)}
);
}
```
## Put it all together
You've built all your components, time to put them together.
**File name: app/page.tsx**
```tsx
import { Festival } from "@/app/components/Festival";
import { NewBand } from "@/app/components/NewBand";
export default function Home() {
return (
🎪 My Festival
);
}
```
You should now be able to add a band to your festival, and see it appear in the list!
**Congratulations! 🎉** You've built your first Jazz app!
You've begun to scratch the surface of what's possible with Jazz. Behind the scenes, your local-first JazzFest app is **already** securely syncing your data to the cloud in real-time, ready for you to build more and more powerful features.
Psst! Got a few more minutes and want to add Server Side Rendering to your app? [We've got you covered!](/docs/server-side/ssr)
## Next steps
* [Add authentication](/docs/key-features/authentication/quickstart) to your app so that you can log in and view your data wherever you are!
* Dive deeper into the collaborative data structures we call [CoValues](/docs/core-concepts/covalues/overview)
* Learn how to share and [collaborate on data](/docs/permissions-and-sharing/overview) using groups and permissions
* Complete the [server-side quickstart](/docs/server-side/quickstart) to learn more about Jazz on the server
### Installation
# Providers
`` is the core component that connects your React application to Jazz. It handles:
* **Data Synchronization**: Manages connections to peers and the Jazz cloud
* **Local Storage**: Persists data locally between app sessions
* **Schema Types**: Provides APIs for the [AccountSchema](/docs/core-concepts/schemas/accounts-and-migrations)
* **Authentication**: Connects your authentication system to Jazz
Our [Chat example app](https://jazz.tools/examples#chat) provides a complete implementation of JazzReactProvider with authentication and real-time data sync.
## Setting up the Provider
The provider accepts several configuration options:
```tsx
import { JazzReactProvider } from "jazz-tools/react";
import { MyAppAccount } from "./schema";
export function MyApp({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
**Info: Tip**
Sign up for a free API key at [dashboard.jazz.tools](https://dashboard.jazz.tools) for higher limits or production use, or use your email address as a temporary key to get started quickly.
**File name: .env**
```bash
NEXT_PUBLIC_JAZZ_API_KEY="you@example.com" # or your API key
```
## Provider Options
### Sync Options
The `sync` property configures how your application connects to the Jazz network:
```ts
import { type SyncConfig } from "jazz-tools";
export const syncConfig: SyncConfig = {
// Connection to Jazz Cloud or your own sync server
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
// When to sync: "always" (default), "never", or "signedUp"
when: "always",
};
```
See [Authentication States](/docs/key-features/authentication/authentication-states#controlling-sync-for-different-authentication-states) for more details on how the `when` property affects synchronization based on authentication state.
### Account Schema
The `AccountSchema` property defines your application's account structure:
```tsx
import { JazzReactProvider } from "jazz-tools/react";
import { MyAppAccount } from "./schema";
export function MyApp({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
### Additional Options
The provider accepts these additional options:
```tsx
export function MyApp({ children }: { children: React.ReactNode }) {
return (
{
console.log("User logged out");
}}
// Handle anonymous account data when user logs in to existing account
onAnonymousAccountDiscarded={(account) => {
console.log("Anonymous account discarded", account.$jazz.id);
// Migrate data here
return Promise.resolve();
}}
>
{children}
);
}
```
See [Authentication States](/docs/key-features/authentication/authentication-states) for more information on authentication states, guest mode, and handling anonymous accounts.
## Authentication
The Jazz Provider works with various authentication methods to enable users to access their data across multiple devices. For a complete guide to authentication, see our [Authentication Overview](/docs/key-features/authentication/overview).
## Need Help?
If you have questions about configuring the Jazz Provider for your specific use case, [join our Discord community](https://discord.gg/utDMjHYg42) for help.
### API Reference
# CoValues API Reference
Understanding how to work with CoValues is critical to building apps with Jazz. This reference guide is intended to help you get up and running by quickly demonstrating the most common use cases. For more in depth detail, you should review the linked dedicated pages.
If you have any questions, we'd be happy to chat on our [Discord server](https://discord.gg/utDMjHYg42)!
| TypeScript Type | Corresponding CoValue | Usage |
| -------------------------- | -------------------------- | ------------------------------------------------------- |
| object | **CoMap** | Key-value stores with pre-defined keys (struct-like) |
| Record | **CoRecord** | Key-value stores with arbitrary string keys (dict-like) |
| T\[\] | **CoList** | Lists |
| T\[\] (append-only) | **CoFeed** | Session-based append-only lists |
| string | **CoPlainText/CoRichText** | Collaborative text |
| Blob \| File | **FileStream** | Files |
| Blob \| File (image) | **ImageDefinition** | Images |
| number\[\] \| Float32Array | **CoVector** | Embeddings |
| T \| U (discriminated) | **DiscriminatedUnion** | Lists of different types of items |
## Defining Schemas
CoValues are defined using schemas which combine CoValue types with Zod schemas. You can find out more about schemas in the [schemas](/core-concepts/covalues/overview#start-your-app-with-a-schema) section of the overview guide.
**File name: schema.ts**
```ts
// 1. CoMaps: Object-like with fixed keys
const ToDo = co.map({
task: z.string(),
completed: z.boolean(),
dueDate: z.date().optional(),
});
// 2. CoRecords: Object-like with arbitrary string keys
const PhoneBook = co.record(z.string(), z.string()); // co.record(keyType, valueType)
// 3. CoLists: Array-like ordered list
const ToDoList = co.list(ToDo); // co.list(itemType)
// 4. CoFeeds: Array-like append-only list
const Message = co.map({ text: z.string() });
const ChatMessages = co.feed(Message); // co.feed(itemType)
// 5. CoPlainTexts/CoRichTexts: String-like
const Description = co.plainText(); // or co.richText();
// 6. FileStreams: Blob-like
const UploadedPDF = co.fileStream();
// 7. ImageDefinitions: Blob-like
const UploadedImage = co.image();
// 8. CoVectors: Array-like list of numbers/Float32Array
const Embedding = co.vector(384); // co.vector(dimensions)
// 9. DiscriminatedUnions: Union of different types of items
const ThisSchema = co.map({
type: z.literal("this"),
thisProperty: z.string(),
});
const ThatSchema = co.map({
type: z.literal("that"),
thatProperty: z.string(),
});
const MyThisOrThat = co.discriminatedUnion("type", [ThisSchema, ThatSchema]); // co.discriminatedUnion(discriminatorKey, arrayOfSchemas)
```
You can use the following Zod types to describe primitive data types:
| Type | Usage | Comment |
| ------------------ | -------- | ----------------------------------------------------------------- |
| z.string() | string | For simple strings which don't need character-level collaboration |
| z.number() | number | |
| z.boolean() | boolean | |
| z.date() | Date | |
| z.literal() | literal | For enums — pass possible values as an array |
| z.object() | object | An immutable, **non-collaborative** object |
| z.tuple() | tuple | An immutable, **non-collaborative** array |
| z.optional(schema) | optional | Pass a Zod schema for an optional property with that schema type |
**Info: Tip**
You can also use the `.optional()` method on both CoValue and Zod schemas to mark them as optional.
There are three additional purpose-specific variants of the `CoMap` type you are likely to need while building Jazz applications.
* `co.account()` — a Jazz account
* `co.profile()` — a user profile
* `co.group()` — a group of users
## Creating CoValues
### Explicit Creation
Once you have a schema, you can create new CoValue instances using that schema using the `.create()` static method. You should pass an initial value as the first argument to this method.
| CoValue Type | Example |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CoMap** | Task.create({ task: "Check out Jazz", completed: false }) |
| **CoRecord** | PhoneBook.create({ "Jenny": "867-5309" }) |
| **CoList** | TaskList.create(\[task1, task2\]) |
| **CoPlainText/CoRichText** | Description.create("Hello World") |
| **CoFeed** | ChatMessages.create(\[{ message: "Hello world!" }\]) |
| **FileStream** | UploadedPDF.createFromBlob(myFile) |
| **ImageDefinition** | createImage(myFile) _note that [using the helper](/docs/core-concepts/covalues/imagedef#creating-images) is the preferred way to create an ImageDefinition_ |
| **CoVector** | Embedding.create(\[0.1, 0.2, ...\]) |
| **DiscriminatedUnion** | MyThis.create({ type: "this", ... })_note that you can only instantiate one of the schemas in the union_ |
**Info: create vs. createFromBlob**
`FileStream` CoValues _can_ be created using the `.create()` method. This will create an empty `FileStream` for you to push chunks into (useful for advanced streaming cases). However, in many cases, using `.createFromBlob(blobOrFile)` to create a `FileStream` directly from a `File` or `Blob` will be more convenient.
### Inline Creation
Where a schema has references, you can create nested CoValues one by one and attach them, but Jazz also allows you to create them inline by specifying their initial values.
```ts
const Task = co.map({
title: z.string(),
completed: z.boolean(),
});
const TaskList = co.list(Task);
const taskList = TaskList.create([
{ title: "Task 1", completed: false }, // These will create new Task CoValues
{ title: "Task 2", completed: false }, // both will be inserted into the TaskList
]);
```
### Permissions
When creating any CoValue, the `.create` method accepts an optional options object as the second argument, which allows you to specify the `owner` of the CoValue.
```ts
const group = co.group().create();
const task = Task.create(
{ title: "Buy milk", completed: false },
{ owner: group },
);
```
If you don't pass an `options` object, or if `owner` is omitted, Jazz will check if there are [permissions configured at a schema level](/docs/permissions-and-sharing/overview#defining-permissions-at-the-schema-level).
If no permissions are set at a schema level when creating CoValues inline, a new group will be created extending the **containing CoValue's ownership group**. In the "Inline Creation" example above, a new group would be created for each task, each extending the ownership group of the `taskList` CoList.
It is a good idea to read through the [permissions](/docs/permissions-and-sharing/overview) section to understand how to manage permissions on CoValues, as unlike in other databases, permissions are fundamental to how Jazz works at a low level, rather than a supporting feature.
## Loading and Reading CoValues
In order to read data, you need to [load a CoValue instance](/docs/core-concepts/subscription-and-loading). There are several ways to do this. We recommend using Jazz with a framework, as this allows you to create reactive subscriptions to CoValues easily, but it is also possible to load a CoValue instance using the `.load()` static method on the schema.
Once you have a loaded CoValue instance, you can normally read it similarly to the corresponding TypeScript type.
### CoMap (and the CoRecord sub-type)
Behaves like a TypeScript object **when reading**.
```ts
// CoMap: Access fixed keys
console.log(user.name); // "Alice"
// CoRecord: Access arbitrary keys
const phone = phoneBook["Jenny"];
// Iteration works as with a TypeScript object
for (const [name, number] of Object.entries(phoneBook)) {
console.log(name, number);
}
```
[Read more →](/docs/core-concepts/covalues/comaps)
### CoList
Behaves like a TypeScript array **when reading**.
```ts
const firstTask = taskList[0];
const length = taskList.length;
// Iteration works as with a TypeScript array
taskList.map((task) => console.log(task.title));
for (const task of taskList) {
// Do something
}
```
[Read more →](/docs/core-concepts/covalues/colists)
### CoPlainText/CoRichText
Behaves like a TypeScript string **when reading**.
```ts
// String operations
const summary = description.substring(0, 100);
```
[Read more →](/docs/core-concepts/covalues/cotexts)
**Note**: Although CoPlainTexts/CoRichTexts behave the same as strings in most circumstances, they are not strings. If you need an actual string type, you can use the `toString()` method.
### CoFeed
CoFeeds do not correspond neatly to a TypeScript type. They are collaborative streams of entries split by session/account, and so there are various ways to access the underlying data.
```ts
// Get the feed for a specific session (e.g. this browser tab)
const thisSessionsFeed = chatMessages.perSession[thisSessionId]; // or .inCurrentSession as shorthand
const latestMessageFromThisSession = thisSessionsFeed.value;
const allMessagesFromThisSession = thisSessionsFeed.all;
// Get the feed for a specific account
const accountFeed = chatMessages.perAccount[accountId];
const latestMessageFromThisAccount = accountFeed.value;
const allMessagesFromThisAccount = accountFeed.all;
// Get the feed for my account
const myFeed = chatMessages.byMe; // shorthand for chatMessages.perAccount[myAccountId]
const latestMessageFromMyAccount = myFeed?.value;
const allMessagesFromMyAccount = myFeed?.all;
// Iterate over all entries in a CoFeed
for (const userId of Object.keys(chatMessages.perAccount)) {
const accountFeed = chatMessages.perAccount[userId];
for (const entry of accountFeed.all) {
if (entry.value.$isLoaded) {
console.log(entry.value);
}
}
}
```
The `.all` property allows you to iterate over all entries in a per-session or per-account feed. If you need to convert a feed to an array, you can use `Array.from()` or the spread operator.
[Read more →](/docs/core-concepts/covalues/cofeeds)
CoFeed Structure
```text
┌────────┐
│ CoFeed └────────────────────────────────────────────────────────────┐
│ ┌────────┐ │
│ │ userA └────────────────────────────────────────────────────────┐ │
│ │ ┌───────────┐ │ │
│ │ │ Session 1 └─────────────────────────────────────────────────┐ │ │
│ │ │ ┌────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │ │
│ │ │ │ value: someVal │ │ value: someVal2 │ │ value: someVal3 │ │ │ │
│ │ │ │ by: userA │ │ by: userA │ │ by: userA │ │ │ │
│ │ │ │ madeAt: 10:00 │ │ madeAt: 10:01 │ │ madeAt: 10:02 │ │ │ │
│ │ │ └────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ ┌───────────┐ │ │
│ │ │ Session 2 └─────────────────────────────────────────────────┐ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ value: someVal3 │ │ │ │
│ │ │ │ by: userA │ │ │ │
│ │ │ │ madeAt: 12:00 │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ┌───────┐ │
│ │ userB └─────────────────────────────────────────────────────────┐ │
│ │ ┌───────────┐ │ │
│ │ │ Session 1 └─────────────────────────────────────────────────┐ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ value: someVal4 │ │ │ │
│ │ │ │ by: userB │ │ │ │
│ │ │ │ madeAt: 10:05 │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
### FileStream
FileStreams can be converted to `Blob` types or read as binary chunks.
```ts
// Get raw data chunks and metadata.
// Optionally pass { allowUnfinished: true } to get chunks of a FileStream which is not yet fully synced.
const fileData = fileStream.getChunks({ allowUnfinished: true });
// Convert to a Blob for use in a tag or
}>
;
)
}
```
### Account & Group
Branching does not bring isolation on Account and Group CoValues.
This means that, adding a member on a branched Group will also add the member to the main Group.
```ts
const featureBranch = await Project.load(projectId, {
unstable_branch: { name: "feature-branch" },
});
featureBranch.$isLoaded &&
featureBranch.$jazz.owner.addMember(member, "writer"); // Will also add the member to the main Group
```
If you are modifying an account, be aware that replacing the root or profile will also modify the main account (although updating the properties will happen on the branch).
```tsx
const me = useAccount(MyAccount, {
resolve: { root: true },
unstable_branch: { name: "feature-branch" },
});
me.$isLoaded && me.$jazz.set("root", { value: "Feature Branch" }); // Will also modify the main account
me.$isLoaded && me.root.$jazz.set("value", "Feature Branch"); // This only modifies the branch
```
### Merging Branches
There are two ways to merge a branch in Jazz, each with different characteristics:
#### 1\. Merge loaded values
This method merges all the values that are currently loaded inside the branch. It happens synchronously and there is no possibility of errors because the values are already loaded.
```ts
async function handleSave() {
// Merge all currently loaded values in the branch
branch.$isLoaded && branch.$jazz.unstable_merge();
}
```
This approach is recommended when you can co-locate the merge operation with the branch load, keeping at a glance what the merge operation will affect.
**Info:**
**Important:** The merge operation will only affect values loaded in the current subscription scope. Values loaded via `ensureLoaded` or `subscribe` will not be affected.
#### 2\. Merge with resolve query
This is a shortcut for loading a value and calling `branch.$jazz.unstable_merge()` on it and will fail if the load isn't possible due to permission errors or network issues.
```ts
async function handleSaveWithResolve() {
// Merge the branch changes back to main
await Project.unstable_merge(projectId, {
resolve: {
tasks: { $each: true },
},
branch: { name: "feature-branch" },
});
}
```
This approach is recommended for more complex merge operations where it's not possible to co-locate the merge with the branch load.
#### Best Practices
When using version control with Jazz, always be exhaustive when defining the resolve query to keep the depth of the branch under control and ensure that the merge covers all the branched values.
The mechanism that Jazz uses to automatically load accessed values should be avoided with branching, as it might lead to cases where merge won't reach all the branch changes.
All the changes made to the branch will be merged into the main CoValue, preserving both author and timestamp.
The merge is idempotent, so you can merge the same branch multiple times, the result will always depend on the branch changes and loading state.
The merge operation cascades down to the CoValue's children, but not to its parents. So if you call `unstable_merge()` on a task, only the changes to the task and their children will be merged:
```tsx
async function handleTaskSave(index: number) {
const task = project.tasks[index];
// Only the changes to the task will be merged
task.$jazz.unstable_merge();
}
```
## Conflict Resolution
When conflicts occur (the same field is modified in both the branch and main), Jazz uses a "last writer wins" strategy:
```ts
// Branch modifies priority to "high"
branch.$isLoaded && branch.$jazz.applyDiff({ priority: "high" });
// Meanwhile, main modifies priority to "urgent"
originalProject.$isLoaded &&
originalProject.$jazz.applyDiff({ priority: "urgent" });
// Merge the branch
branch.$isLoaded && branch.$jazz.unstable_merge();
// Main's value ("urgent") wins because it was written later
console.log(originalProject.priority); // "urgent"
```
## Private branches
When the owner is not specified, the branch has the same permissions as the main values.
You can also create a private branch by providing a group owner.
```ts
// Create a private group for the branch
const privateGroup = Group.create();
const privateBranch = Project.load(projectId, {
unstable_branch: {
name: "private-edit",
owner: privateGroup,
},
});
// Only members of privateGroup can see the branch content
// The sync server cannot read the branch content
```
You can use private branches both to make the changes to the branches "private" until merged, or to give controlled write access to a group of users.
Only users with both write access to the main branch and read access to the private branch have the rights to merge the branch.
**Info:**
**Important:** Branch names are scoped to their owner. The same branch name with different owners creates completely separate branches. For example, a branch named "feature-branch" owned by User A is completely different from a branch named "feature-branch" owned by User B.
## Branch Identification
You can get the current branch information from the `$jazz` field.
```ts
const myBranch = await Project.load(projectId, {
unstable_branch: { name: "feature-branch" },
});
console.log(myBranch.$jazz.id); // Branch ID is the same as source
console.log(myBranch.$isLoaded && myBranch.$jazz.branchName); // "feature-branch"
console.log(myBranch.$isLoaded && myBranch.$jazz.isBranched); // true
```
### History
# History
Jazz tracks every change to your data automatically. See who changed what, when they did it, and even look at your data from any point in the past.
See the [version history example](https://github.com/garden-co/jazz/tree/main/examples/version-history) for reference.
Let's use the following schema to see how we can use the edit history.
```ts
export const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "completed"]),
});
export type Task = co.loaded;
```
## The $jazz.getEdits() method
Every CoValue has a `$jazz.getEdits()` method that contains the complete history for each field. Here's how to get the edit history for `task.status`:
```ts
task.$jazz.getEdits().status;
// Returns the latest edit
task.$jazz.getEdits().status?.all;
// Returns array of all edits in chronological order
// Check if edits exist
const statusEdits = task.$jazz.getEdits().status;
if (statusEdits && statusEdits.by?.profile.$isLoaded) {
const name = statusEdits.by.profile.name;
console.log(`Last changed by ${name}`);
}
```
## Edit Structure
Each edit contains:
```ts
const edit = task.$jazz.getEdits().status;
// The edit object contains:
edit?.value; // The new value: "in-progress"
edit?.by; // Account that made the change
edit?.madeAt; // Date when the change occurred
```
## Accessing History
### Latest Edit
Get the most recent change to a field:
```ts
// Direct access to latest edit
const latest = task.$jazz.getEdits().title;
if (latest) {
console.log(`Title is now "${latest.value}"`);
}
```
### All Edits
Get the complete history for a field:
```ts
// Get all edits (chronologically)
const allStatusEdits = task.$jazz.getEdits().status?.all || [];
allStatusEdits.forEach((edit, index) => {
console.log(`Edit ${index}: ${edit.value} at ${edit.madeAt.toISOString()}`);
});
// Edit 0: todo at 2025-05-22T13:00:00.000Z
// Edit 1: in-progress at 2025-05-22T14:00:00.000Z
// Edit 2: completed at 2025-05-22T15:30:00.000Z
```
### Initial Values
The first edit contains the initial value:
```ts
const allEdits = task.$jazz.getEdits().status?.all || [];
const initialValue = allEdits[0]?.value;
console.log(`Started as: ${initialValue}`);
// Started as: todo
```
### Created Date and Last Updated Date
To show created date and last updated date, use the `$jazz.createdAt` and `$jazz.lastUpdatedAt` getters.
```tsx
console.log(new Date(task.$jazz.createdAt));
console.log(new Date(task.$jazz.lastUpdatedAt));
```
### Created By
To show who created the value, use the `$jazz.createdBy` getter. This will return the ID of the user who created the value as a string.
```tsx
console.log(task.$jazz.createdBy);
```
## Requirements
* CoValues must be loaded to access history (see [Subscription & Loading](/docs/core-concepts/subscription-and-loading))
* History is only available for fields defined in your schema
* Edit arrays are ordered chronologically (oldest to newest)
## Common Patterns
For practical implementations using history, see [History Patterns](/docs/reference/design-patterns/history-patterns):
* Building audit logs
* Creating activity feeds
* Implementing undo/redo
* Showing change indicators
* Querying historical data
## Server-Side Development
### Quickstart
# Get started with Server Workers in 10 minutes
This quickstart guide will take you from an empty project to a server worker which can interact with your Jazz application.
* You'll get the most out of this guide if you complete [the frontend quickstart guide](/docs/quickstart) first.
* If you've already completed the frontend quickstart, you can skip straight to [extending your schema](#define-your-schema).
## Create Your App
We'll be using Next.js for simplicity, but you can use any framework you like.
You can accept the defaults for all the questions, or customise the project as you like.
```sh
npm create-next-app@latest --typescript jazzfest
cd jazzfest
```
**Info:**
Requires Node.js 20+
## Install Jazz
The `jazz-tools` package includes everything you're going to need to build your first Jazz server worker.
```sh
npm install jazz-tools
```
## Set your API key
Sign up for a free API key at [dashboard.jazz.tools](https://dashboard.jazz.tools) for higher limits or production use, or use your email address as a temporary key to get started quickly.
**File name: .env**
```bash
NEXT_PUBLIC_JAZZ_API_KEY="you@example.com" # or your API key
```
## Define your schema
We're going to define a simple schema for our server worker. We'll use the `root` on the worker to store a list of bands. We're also going to add a migration to initialise the `root` if it doesn't exist.
**File name: app/schema.ts**
```ts
import { co, z } from "jazz-tools";
export const Band = co.map({
name: z.string(),
});
export const BandList = co.list(Band);
export const JazzFestWorkerAccount = co
.account({
root: co.map({
bandList: BandList,
}),
profile: co.profile(),
})
.withMigration(async (account) => {
if (!account.$jazz.has("root")) {
account.$jazz.set("root", {
bandList: [],
});
if (account.root.$isLoaded) {
account.root.$jazz.owner.makePublic();
}
}
});
```
**Info:**
If you're continuing from the [front-end Quickstart](/docs/quickstart), you can extend your existing schema.
## Create a Server Worker
Jazz provides a CLI to create server workers. You can create a server worker using the following command:
```sh
npx jazz-run account create --name "JazzFest Server Worker"
```
You can copy the output of this command and paste it directly into your `.env` file:
**File name: .env**
```bash
NEXT_PUBLIC_JAZZ_API_KEY=you@example.com # or your API key
#[!code ++:2]
NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT=co_z...
JAZZ_WORKER_SECRET=sealerSecret_z.../signerSecret_z...
```
**Warning:**
Your `JAZZ_WORKER_SECRET` should **never** be exposed to the client.
## Defining your HTTP request schema
Next, we're going to set up an HTTP request schema to define our request and response. Here, we tell Jazz that we will send a `Band` under the key `band` and expect a `bandList` in response, which is a list of `Band`s.
We also need to tell Jazz which keys should be treated as loaded in the request and response using the `resolve` query.
**File name: app/announceBandSchema.ts**
```ts
import { experimental_defineRequest } from "jazz-tools";
import { Band, BandList } from "./schema";
const workerId = process.env.NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT;
if (!workerId) throw new Error("NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT is not set");
export const announceBand = experimental_defineRequest({
url: "/api/announce-band",
workerId: workerId,
request: { schema: { band: Band }, resolve: { band: true } },
response: {
schema: { bandList: BandList },
resolve: { bandList: { $each: true } },
},
});
```
## Configure your Server Worker
We're going to use the `startWorker` function to start our server worker, and register a `POST` handler, which will listen for the requests being sent to our server worker.
We'll also use a `resolve` query here to make sure that the `bandList` is loaded on the worker's root.
**File name: app/api/announce-band/route.ts**
```ts
import { startWorker } from "jazz-tools/worker";
import { announceBand } from "@/app/announceBandSchema";
import { JazzFestWorkerAccount } from "./schema";
const { worker } = await startWorker({
syncServer: `wss://cloud.jazz.tools/?key=${process.env.NEXT_PUBLIC_JAZZ_API_KEY}`,
accountID: process.env.NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
AccountSchema: JazzFestWorkerAccount,
});
export async function POST(request: Request) {
return announceBand.handle(request, worker, async ({ band }) => {
if (!band) {
throw new Error("Band is required");
}
const {
root: { bandList },
} = await worker.$jazz.ensureLoaded({
resolve: {
root: {
bandList: true,
},
},
});
bandList.$jazz.push(band);
return { bandList };
});
}
```
## Start your server worker
We can now start our development server to make sure everything is working.
```bash
npm run dev
```
If you open your browser, you should see the default Next.js welcome page.
### Not working?
* Check you set up your `.env` file correctly with `NEXT_PUBLIC_` where necessary
* Check you're importing `startWorker` from `jazz-tools/worker`
**Info: Still stuck?** Ask for help on [Discord](https://discord.gg/utDMjHYg42)!
## Send requests to your server worker
### Creating a Jazz Client
_If you already have a working provider from the frontend quickstart, you can skip this step._
We're going to wrap our Next.js app in a `JazzReactProvider` so that we can use Jazz on our client.
**File name: app/layout.tsx**
```tsx
import { JazzReactProvider } from "jazz-tools/react";
const apiKey = process.env.NEXT_PUBLIC_JAZZ_API_KEY;
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
### Creating your page component
We're going to send a request to our server worker to announce a new band. Our worker will respond with a list of bands that we can display on our page.
**File name: app/page.tsx**
```tsx
"use client";
import type { co } from "jazz-tools";
import { useState } from "react";
import { announceBand } from "@/app/announceBandSchema";
import type { BandList } from "./schema";
export default function Home() {
const [bandName, setBandName] = useState("");
const [bandList, setBandList] =
useState>();
const handleAnnounceBand = async () => {
const bandListResponse = await announceBand.send({
band: { name: bandName },
});
setBandName("");
if (bandListResponse.bandList.$isLoaded) {
setBandList(bandListResponse.bandList);
}
};
return (
setBandName(e.target.value)}
/>
{bandList?.$isLoaded &&
bandList.map(
(band) => band &&
{band.name}
,
)}
);
}
```
## Try it out!
Your browser should now be showing you a page with an input field and a button. If you enter a band name and click the button, your server worker will receive the request and add the band to the list.
**Congratulations! 🎉** You've just built your first Jazz server worker!
This simple pattern is the foundation for building powerful, real-time applications.
Here are some ideas about what you could use your server worker for:
* integrating with payment providers
* sending emails/SMSes
* gathering data from external APIs
* managing authoritative state
Looking forward to seeing what you build!
## Next steps
* Complete the [front-end quickstart](/docs/quickstart) to learn more about how to build real-time UIs using Jazz
* Find out how to [handle errors](/docs/server-side/jazz-rpc#error-handling) gracefully in your server worker
* Learn how to share and [collaborate on data](/docs/permissions-and-sharing/overview) in groups with complex permissions
### Setup
# Running Jazz on the server
Jazz is a distributed database that can be used on both clients or servers without any distinction.
You can use servers to:
* perform operations that can't be done on the client (e.g. sending emails, making HTTP requests, etc.)
* validate actions that require a central authority (e.g. a payment gateway, booking a hotel, etc.)
We call the code that runs on the server a "Server Worker".
The main difference to keep in mind when working with Jazz compared to traditional systems is that server code doesn't have any special or privileged access to the user data. You need to be explicit about what you want to share with the server.
This means that your server workers will have their own accounts, and they need to be explicitly given access to the CoValues they need to work on.
## Generating credentials
Server Workers typically have static credentials, consisting of a public Account ID and a private Account Secret.
To generate new credentials for a Server Worker, you can run:
```sh
npx jazz-run account create --name "My Server Worker"
```
The name will be put in the public profile of the Server Worker's `Account`, which can be helpful when inspecting metadata of CoValue edits that the Server Worker has done.
**Info: Note**
By default the account will be stored in Jazz Cloud. You can use the `--peer` flag to store the account on a different sync server.
## Running a server worker
You can use `startWorker` to run a Server Worker. Similarly to setting up a client-side Jazz context, it:
* takes a custom `AccountSchema` if you have one (for example, because the worker needs to store information in its private account root)
* takes a URL for a sync & storage server
The migration defined in the `AccountSchema` will be executed every time the worker starts, the same way as it would be for a client-side Jazz context.
```ts
import { startWorker } from "jazz-tools/worker";
const { worker } = await startWorker({
AccountSchema: MyWorkerAccount,
syncServer: `wss://cloud.jazz.tools/?key=${apiKey}`,
accountID: process.env.JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
});
```
`worker` is an instance of the `Account` schema provided, and acts like `me` (as returned by `useAccount` on the client).
It will implicitly become the current account, and you can avoid mentioning it in most cases.
For this reason we also recommend running a single worker instance per server, because it makes your code much more predictable.
In case you want to avoid setting the current account, you can pass `asActiveAccount: false` to `startWorker`.
## Storing & providing credentials
Server Worker credentials are typically stored and provided as environment variables.
**Take extra care with the Account Secret — handle it like any other secret environment variable such as a DB password.**
## Next steps
For configuring crypto implementations and choosing deployment strategies for different runtimes, see [Deployment](/docs/server-side/deployment).
### Overview
# Communicating with Server Workers
Server Workers in Jazz can receive data from clients through two different APIs, each with their own characteristics and use cases. This guide covers the key properties of each approach to help you choose the right one for your application.
## Overview
Jazz provides three ways to communicate with Server Workers:
1. **JazzRPC** \- A simple, yet powerful RPC system that allows you to call functions on Server Workers from the client side.
2. **HTTP Requests** \- The easiest to work with and deploy, ideal for simple communication with workers.
3. **Inbox** \- Fully built using the Jazz data model with offline support
## JazzRPC (Recommended)
JazzRPC is the most straightforward way to communicate with Server Workers. It works well with any framework or runtime that supports standard Request and Response objects, can be scaled horizontally, and put clients and workers in direct communication.
### When to use JazzRPC
Use JazzRPC when you need immediate responses, are deploying to serverless environments, need horizontal scaling, or are working with standard web frameworks.
It's also a good solution when using full-stack frameworks like Next.js, where you can use the API routes to handle the server-side logic.
[Learn more about JazzRPC →](/docs/server-side/jazz-rpc)
## HTTP Requests
If all you need is basic authentication when communicating with a worker, you can use Regular HTTP requests. They are the easiest to work with and deploy, ideal for simple communication with workers.
HTTP requests are the easiest way to communicate with Server Workers. They don't come with any of the benefits of JazzRPC, but are a good solution for simple communication with workers.
### When to use HTTP Requests
Use HTTP requests when you don't need the advanced features of JazzRPC, but you need to communicate with a worker from a serverless environment or a standard web framework and need basic authentication.
[Learn more about HTTP Requests →](/docs/server-side/communicating-with-workers/http-requests)
## Inbox
The Inbox API is fully built using the Jazz data model and provides offline support. Requests and responses are synced as soon as the device becomes online, but require the Worker to always be online to work properly.
### When to use Inbox
Use Inbox when you need offline support, want to leverage the Jazz data model, can ensure the worker stays online, need persistent message storage, or want to review message history.
It works great when you don't want to expose your server with a public address, because it uses Jazz's sync to make the communication happen.
Since Jazz handles all the network communication, the entire class of network errors that usually come with traditional HTTP requests are not a problem when using the Inbox API.
[Learn more about Inbox →](/docs/server-side/communicating-with-workers/inbox)
### JazzRPC
# JazzRPC
JazzRPC is the most straightforward and complete way to securely communicate with Server Workers. It works well with any framework or runtime that supports standard Request and Response objects, can be scaled horizontally, and puts clients and workers in direct communication.
## Setting up JazzRPC
### Defining request schemas
Use `experimental_defineRequest` to define your API schema:
```ts
import { experimental_defineRequest, z } from "jazz-tools";
import { Event, Ticket } from "@/lib/schema";
const workerId = process.env.NEXT_PUBLIC_JAZZ_WORKER_ACCOUNT!;
export const bookEventTicket = experimental_defineRequest({
url: "/api/book-event-ticket",
// The id of the worker Account or Group
workerId,
// The schema definition of the data we send to the server
request: {
schema: {
event: Event,
},
// The data that will be considered as "loaded" in the server input
resolve: {
event: { reservations: true },
},
},
// The schema definition of the data we expect to receive from the server
response: {
schema: { ticket: Ticket },
// The data that will be considered as "loaded" in the client response
// It defines the content that the server directly sends to the client, without involving the sync server
resolve: { ticket: true },
},
});
```
### Setting up the Server Worker
We need to start a Server Worker instance that will be able to sync data with the sync server, and handle the requests.
```ts
import { startWorker } from "jazz-tools/worker";
export const jazzServer = await startWorker({
syncServer: "wss://cloud.jazz.tools/?key=your-api-key",
accountID: process.env.JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
});
```
## Handling JazzRPC requests on the server
### Creating API routes
Create API routes to handle the defined RPC requests. Here's an example using Next.js API routes:
```ts
import { jazzServer } from "@/jazzServer";
import { Ticket } from "@/lib/schema";
import { bookEventTicket } from "@/bookEventTicket";
import { Group, JazzRequestError } from "jazz-tools";
export async function POST(request: Request) {
return bookEventTicket.handle(
request,
jazzServer.worker,
async ({ event }, madeBy) => {
const ticketGroup = Group.create(jazzServer.worker);
const ticket = Ticket.create({
account: madeBy,
event,
});
// Give access to the ticket to the client
ticketGroup.addMember(madeBy, "reader");
event.reservations.$jazz.push(ticket);
return {
ticket,
};
},
);
}
```
## Making requests from the client
### Using the defined API
Make requests from the client using the defined API:
```ts
import { bookEventTicket } from "@/bookEventTicket";
import { Event } from "@/lib/schema";
import { co, isJazzRequestError } from "jazz-tools";
export async function sendEventBookingRequest(event: co.loaded) {
const { ticket } = await bookEventTicket.send({ event });
return ticket;
}
export async function sendEventBookingRequest(event: co.loaded) {
try {
const { ticket } = await bookEventTicket.send({ event });
return ticket;
} catch (error) {
// This works as a type guard, so you can easily get the error message and details
if (isJazzRequestError(error)) {
alert(error.message);
return;
}
}
}
```
## Error handling
### Server-side error handling
Use `JazzRequestError` to return proper HTTP error responses:
```ts
export async function POST(request: Request) {
return bookEventTicket.handle(
request,
jazzServer.worker,
async ({ event }, madeBy) => {
// Check if the event is full
if (event.reservations.length >= event.capacity) {
// The JazzRequestError is propagated to the client, use it for any validation errors
throw new JazzRequestError("Event is full", 400);
}
const ticketGroup = Group.create(jazzServer.worker);
const ticket = Ticket.create({
account: madeBy,
event,
});
// Give access to the ticket to the client
ticketGroup.addMember(madeBy, "reader");
event.reservations.$jazz.push(ticket);
return {
ticket,
};
},
);
}
```
**Info: Note**
To ensure that the limit is correctly enforced, the handler should be deployed in a single worker instance (e.g. a single Cloudflare DurableObject).
Details on how to deploy a single instance Worker are available in the [Deployment patterns](/docs/server-side/deployment#deployment-patterns) section.
### Client-side error handling
Handle errors on the client side:
```ts
export async function sendEventBookingRequest(event: co.loaded) {
try {
const { ticket } = await bookEventTicket.send({ event });
return ticket;
} catch (error) {
// This works as a type guard, so you can easily get the error message and details
if (isJazzRequestError(error)) {
alert(error.message);
return;
}
}
}
```
**Info: Note**
The `experimental_defineRequest` API is still experimental and may change in future versions. For production applications, consider the stability implications.
## Security safeguards provided by JazzRPC
JazzRPC includes several built-in security measures to protect against common attacks:
### Cryptographic Authentication
* **Digital Signatures**: Each RPC is cryptographically signed using the sender's private key
* **Signature Verification**: The server verifies the signature using the sender's public key to ensure message authenticity and to identify the sender account
* **Tamper Protection**: Any modification to the request payload will invalidate the signature
### Replay Attack Prevention
* **Unique Message IDs**: Each RPC has a unique identifier (`co_z${string}`)
* **Duplicate Detection**: incoming messages ids are tracked to prevent replay attacks
* **Message Expiration**: RPCs expire after 60 seconds to provide additional protection
These safeguards ensure that JazzRPC requests are secure, authenticated, and protected against common attack vectors while maintaining the simplicity of standard HTTP communication.
## See also
* [Deployment patterns](/docs/server-side/deployment#deployment-patterns) for guidance on single-instance vs. multi-region deployments.
### HTTP requests
# HTTP Requests with Server Workers
HTTP requests are the simplest way to communicate with Server Workers. While they don't provide all the features of [JazzRPC](/docs/server-side/jazz-rpc), they are a good solution when all you need is basic authentication.
They work by generating a short-lived token with `generateAuthToken` and attaching it to the request headers as `Authorization: Jazz `. The server can then verify the token with `authenticateRequest` and get the account that the request was made by.
**Info: Note**
While the token is cryptographically secure, using non secure connections still makes you vulnerable to MITM attacks as - unlike JazzRPC - the request is not signed.
Replay attacks are mitigated by token expiration (default to 1 minute), but it's up to you to ensure that the token is not reused.
It is recommended to use HTTPS whenever possible.
## Creating a Request
You can use any method to create a request; the most common is the `fetch` API.
By default, the token is expected to be in the `Authorization` header in the form of `Jazz `.
```ts
import { generateAuthToken } from "jazz-tools";
const response = await fetch("https://example.com", {
headers: {
Authorization: `Jazz ${generateAuthToken()}`,
},
});
```
## Authenticating requests
You can use the `authenticateRequest` function to authenticate requests.
Attempting to authenticate a request without a token doesn't fail; it returns `account` as `undefined`. For endpoints that **require** authentication, ensure `account` is defined in addition to any permission checks you may need.
```ts
import { authenticateRequest } from "jazz-tools";
import { startWorker } from "jazz-tools/worker";
export async function GET(request: Request) {
const worker = await startWorker({
syncServer: "wss://cloud.jazz.tools/?key=your-api-key",
accountID: process.env.JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
asActiveAccount: true,
});
const { account, error } = await authenticateRequest(request);
// There was an error validating the token (e.g., invalid or expired)
if (error) {
return new Response(JSON.stringify(error), { status: 401 });
}
if (!account) {
return new Response("Unauthorized", { status: 401 });
}
return new Response(
JSON.stringify({
message: `The request was made by ${account.$jazz.id}`,
}),
);
}
```
## Multi-account environments
If you are using multiple accounts in your environment - for instance if your server starts multiple workers - or in general if you need to send and authenticate requests as a specific account, you can specify which one to use when generating the token or when authenticating the request.
### Making a request as a specific account
`generateAuthToken` accepts an optional account parameter, so you can generate a token for a specific account.
```ts
const response = await fetch("https://example.com", {
headers: {
Authorization: `Jazz ${generateAuthToken(account)}`,
},
});
```
### Authenticating a request as a specific account
Similarly, specify the account used to verify the token via the `loadAs` option:
```ts
import { authenticateRequest } from "jazz-tools";
import { startWorker } from "jazz-tools/worker";
export async function GET(request: Request) {
const { worker } = await startWorker({
syncServer: "wss://cloud.jazz.tools/?key=your-api-key",
accountID: process.env.JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
});
const { account, error } = await authenticateRequest(request, {
loadAs: worker,
});
}
```
## Custom token expiration
You can specify the expiration time of the token using the `expiration` option. The default expiration time is 1 minute.
```ts
import { authenticateRequest } from "jazz-tools";
export async function GET(request: Request) {
const { account, error } = await authenticateRequest(request, {
expiration: 1000 * 60 * 60 * 24, // 24 hours
});
}
```
## Custom token location
While using the `Authorization` header using the `Jazz ` format is the most common way to send the token, you can provide the token in any other way you want.
For example, you can send the token in the `x-jazz-auth-token` header:
```ts
import { generateAuthToken } from "jazz-tools";
const response = await fetch("https://example.com", {
headers: {
"x-jazz-auth-token": generateAuthToken(),
},
});
```
Then you can specify the location of the token using the `getToken` option:
```ts
import { authenticateRequest } from "jazz-tools";
export async function GET(request: Request) {
const { account, error } = await authenticateRequest(request, {
getToken: (request) => request.headers.get("x-jazz-auth-token"),
});
}
```
## Manual token parsing
If you need to manually parse a token from a string, you can use the `parseAuthToken` function.
```ts
import { parseAuthToken, generateAuthToken } from "jazz-tools";
const myToken = generateAuthToken();
const { account, error } = await parseAuthToken(myToken);
```
### Inbox API
# Inbox API with Server Workers
The Inbox API provides a message-based communication system for Server Workers in Jazz.
It works on top of the Jazz APIs and uses sync to transfer messages between the client and the server.
## Setting up the Inbox API
### Define the inbox message schema
Define the inbox message schema in your schema file:
```ts
export const BookTicketMessage = co.map({
type: z.literal("bookTicket"),
event: Event,
});
```
Any kind of CoMap is valid as an inbox message.
### Setting up the Server Worker
Run a server worker and subscribe to the `inbox`:
```ts
import { Account, co, Group } from "jazz-tools";
import { startWorker } from "jazz-tools/worker";
import { BookTicketMessage, Ticket } from "@/lib/schema";
const {
worker,
experimental: { inbox },
} = await startWorker({
accountID: process.env.JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
syncServer: "wss://cloud.jazz.tools/?key=your-api-key",
});
inbox.subscribe(BookTicketMessage, async (message, senderID) => {
const madeBy = await co.account().load(senderID, { loadAs: worker });
const { event } = await message.$jazz.ensureLoaded({
resolve: {
event: {
reservations: true,
},
},
});
const ticketGroup = Group.create(worker);
const ticket = Ticket.create({
account: madeBy,
event,
});
if (madeBy.$isLoaded) {
// Give access to the ticket to the client
ticketGroup.addMember(madeBy, "reader");
event.reservations.$jazz.push(ticket);
}
return ticket;
});
```
### Handling multiple message types
`inbox.subscribe` should be called once per worker instance.
If you need to handle multiple message types, you can use the `co.discriminatedUnion` function to create a union of the message types.
```ts
const CancelReservationMessage = co.map({
type: z.literal("cancelReservation"),
event: Event,
ticket: Ticket,
});
export const InboxMessage = co.discriminatedUnion("type", [
BookTicketMessage,
CancelReservationMessage,
]);
```
And check the message type in the handler:
```ts
import { InboxMessage } from "@/lib/schema";
inbox.subscribe(InboxMessage, async (message, senderID) => {
switch (message.type) {
case "bookTicket":
return await handleBookTicket(message, senderID);
case "cancelReservation":
return await handleCancelReservation(message, senderID);
}
});
```
## Sending messages from the client
### Using the Inbox Sender hook
Use `experimental_useInboxSender` to send messages from React components:
```ts
import { co } from "jazz-tools";
import { experimental_useInboxSender } from "jazz-tools/react";
import { BookTicketMessage, Event } from "@/lib/schema";
function EventComponent({ event }: { event: co.loaded }) {
const sendInboxMessage = experimental_useInboxSender(process.env.WORKER_ID);
const [isLoading, setIsLoading] = useState(false);
const onBookTicketClick = async () => {
setIsLoading(true);
const ticketId = await sendInboxMessage(
BookTicketMessage.create({
type: "bookTicket",
event: event,
}),
);
alert(`Ticket booked: ${ticketId}`);
setIsLoading(false);
};
return (
);
}
```
The `sendInboxMessage` API returns a Promise that waits for the message to be handled by a Worker. A message is considered to be handled when the Promise returned by `inbox.subscribe` resolves. The value returned will be the id of the CoValue returned in the `inbox.subscribe` resolved promise.
## Deployment considerations
Multi-region deployments are not supported when using the Inbox API.
If you need to split the workload across multiple regions, you can use the [HTTP API](/docs/server-side/communicating-with-workers/http-requests) instead.
### Deployment
# Deploying Server Workers
This page covers runtime-specific configuration and deployment strategies for Jazz server workers. For setting up your server worker code, see [Setup](/docs/server-side/setup).
## Crypto implementations
Jazz uses a WASM-based crypto implementation by default, providing near-native performance with broad compatibility. Depending on your deployment target, you may want to use the edge WASM loader or the native Node-API implementation.
## WASM on Edge runtimes
On some edge platforms, such as Cloudflare Workers or Vercel Edge Functions, environment security restrictions may trigger WASM crypto to fail.
To avoid this failure, you can ensure that Jazz uses the WASM implementation by importing the WASM loader before using Jazz. For example:
```ts
import "jazz-tools/load-edge-wasm";
// Other Jazz Imports
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
// Jazz application logic
return new Response("Hello from Jazz on Cloudflare!");
},
};
```
Currently, the Jazz Loader is tested on the following edge environments:
* Cloudflare Workers
* Vercel Functions
### Requirements
* Edge runtime environment that supports WebAssembly
* `jazz-tools/load-edge-wasm` must be imported before any Jazz import
## Node-API
For even higher performance on Node.js or Deno, you can enable the native crypto (Node-API) implementation. Node-API is Node.js's native API for building modules in Native Code (Rust/C++) that interact directly with the underlying system, allowing for true native execution speed.
You can use it as follows:
```ts
import { startWorker } from "jazz-tools/worker";
import { NapiCrypto } from "jazz-tools/napi";
const { worker } = await startWorker({
syncServer: `wss://cloud.jazz.tools/?key=${apiKey}`,
accountID: process.env.JAZZ_WORKER_ACCOUNT,
accountSecret: process.env.JAZZ_WORKER_SECRET,
crypto: await NapiCrypto.create(),
});
```
**Info: Note**
The Node-API implementation is not available on all platforms. It is only available on Node.js 20.x and higher. The supported platforms are:
* macOS (x64, ARM64)
* Linux (x64, ARM64, ARM, musl)
It does not work in edge runtimes.
### On Next.js
In order to use Node-API with Next.js, you need to tell Next.js to bundle the native modules in your build.
You can do this by adding the required packages to the [serverExternalPackages](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) array in your `next.config.js`.
**Note**: if you're deploying to Vercel, be sure to use the `nodejs` runtime!
**File name: next.config.js**
```ts
module.exports = {
serverExternalPackages: [
"cojson-core-napi",
"cojson-core-napi-linux-x64-gnu",
"cojson-core-napi-linux-x64-musl",
"cojson-core-napi-linux-arm64-gnu",
"cojson-core-napi-linux-arm64-musl",
"cojson-core-napi-darwin-x64",
"cojson-core-napi-darwin-arm64",
"cojson-core-napi-linux-arm-gnueabihf",
],
};
```
## Deployment patterns
### Single Instance Requirements
Some operations need to happen one at a time and in the same place, otherwise the data can get out of sync.
For example, if you are checking capacity for an event and creating tickets, you must ensure only one server is doing it. If multiple servers check at the same time, they might all think there is space and allow too many tickets.
Jazz uses eventual consistency (data takes a moment to sync between regions), so this problem is worse if you run multiple server copies in different locations.
Until Jazz supports transactions across regions, the solution is to deploy a single server instance for these sensitive operations.
Examples of when you must deploy on a single instance are:
1. Distribute a limited number of tickets
* Limiting ticket sales so that only 100 tickets are sold for an event.
* The check ("is there space left?") and ticket creation must happen together, or you risk overselling.
2. Inventory stock deduction
* Managing a product stock count (e.g., 5 items left in store).
* Multiple instances could let multiple buyers purchase the last item at the same time.
3. Sequential ID or token generation
* Generating unique incremental order numbers (e.g., #1001, #1002).
* Multiple instances could produce duplicates if not coordinated.
S