Skip to main content
Data IntegrityData Integrity

Data Integrity

Strong inferred types; single source of truth that is referentially stable ensures consistency; asynchronous invariants make it easy to avoid race conditions

Created by Hea Poh Lin

Performance

Normalized cache means data is often ready before it is even needed. Automatic request deduplication means less data to send over the network.

Composition over configuration

Declare what you need where you need it. Share data definitions across platforms, components, protocols, and behaviors.

Incremental Adoption

Get started fast with one line data definition and one line data binding. Then add TypeScript, normalized cache with Schemas, optimistic updates and more.

A simple data fetch

Add a single useSuspense() call where you need its data.

Rest Hooks automatically optimizes performance by caching the results, deduplicating fetches, efficient component render bindings and more.

Live Editor
const getTodo = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id'
});
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(getTodo, { id });
return <div>{todo.title}</div>;
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store
Live Editor
class Todo extends Entity {
id = 0;
userId = 0;
title = '';
completed = false;
pk() { return this.id }
}
const TodoResource = createResource({
path: 'https\\://jsonplaceholder.typicode.com/todos/:id',
schema: Todo,
})
function TodoDetail({ id }: { id: number }) {
const todo = useSuspense(TodoResource.get, { id });
return <div>{todo.title}</div>;
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store
Live Editor
const gql = new GQLEndpoint('/');
const todoDetail = gql.query(`
query GetTodo($id: ID!) {
todo(id: $id) {
id
title
completed
}
}
`);
function TodoDetail({ id }: { id: number }) {
const { todo } = useSuspense(todoDetail, { id });
return <div>{todo.title}</div>;
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store

Stateful mutations

Use Controller.fetch() to update the store.

Rest Hooks ensures data consistency and integrity globally. Every piece of data maintains referential stability unless it changes. This ensures the most optimized render performance, as well as predictable equality checks.

Live Editor
function TodoDetail({ id }) {
const todo = useSuspense(TodoResource.get, { id });
const controller = useController();
const updateWith = title => () =>
controller.fetch(
TodoResource.partialUpdate,
{ id },
{ title }
);
return (
<div>
<div>{todo.title}</div>
<button onClick={updateWith('🥑')}>🥑</button>
<button onClick={updateWith('💖')}>💖</button>
</div>
);
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store
Live Editor
const gql = new GQLEndpoint('/');
class Todo extends GQLEntity {
readonly title: string = '';
readonly completed: boolean = false;
}
const getTodo = gql.query(`
query GetTodo($id: ID!) {
todo(id: $id) {
id
title
completed
}
}
`, { todo: Todo });
const updateTodo = gql.mutation(
`mutation UpdateTodo($todo: Todo!) {
updateTodo(todo: $todo) {
id
title
completed
}
}`,
{ updateTodo: Todo },
);
function TodoDetail({ id }) {
const { todo } = useSuspense(getTodo, { id });
const controller = useController();
const updateWith = title => () =>
controller.fetch(
updateTodo,
{ todo: { id, title } }
);
return (
<div>
<div>{todo.title}</div>
<button onClick={updateWith('🥑')}>🥑</button>
<button onClick={updateWith('💖')}>💖</button>
</div>
);
}
render(<TodoDetail id={1} />);
Live Preview
Loading...
Store

An application

Data can be consumed and controlled in many contexts, speeding up development.

Rest Hooks uses data normalization to maintain consistency no matter how and where the data is consumed.

Rest easy with the help of debugging, unit testing, and storybook integration.

Live Editor
function TodoItem({ todo }: { todo: Todo }) {
const controller = useController();
return (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={e =>
controller.fetch(
TodoResource.partialUpdate,
{ id: todo.id },
{ completed: e.currentTarget.checked },
)
}
/>
{todo.completed ? (
<strike>{todo.title}</strike>
) : (
todo.title
)}
<a
style={{ cursor: 'pointer' }}
onClick={() =>
controller.fetch(TodoResource.delete, {
id: todo.id,
})
}
>
</a>
</div>
);
}
function TodoList() {
const todos = useSuspense(TodoResource.getList);
const controller = useController();
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.pk()} todo={todo} />
))}
</div>
);
}
render(<TodoList />);
Live Preview
Loading...
Store
Live Editor
const gql = new GQLEndpoint('/');
class Todo extends GQLEntity {
readonly title: string = '';
readonly completed: boolean = false;
}
const todoList = gql.query(`
query GetTodos {
todo {
id
title
completed
}
}
`, { todos: [Todo] });
const updateTodo = gql.mutation(
`mutation UpdateTodo($todo: Todo!) {
updateTodo(todo: $todo) {
id
title
completed
}
}`,
{ updateTodo: Todo },
);
function TodoItem({ todo }: { todo: TodoResource }) {
const controller = useController();
return (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={e =>
controller.fetch(updateTodo, {
todo: { id: todo.id, completed: e.currentTarget.checked },
})
}
/>
{todo.completed ? <strike>{todo.title}</strike> : todo.title}
</div>
);
}
function TodoList() {
const { todos } = useSuspense(todoList, {});
const controller = useController();
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.pk()} todo={todo} />
))}
</div>
);
}
render(<TodoList />);
Live Preview
Loading...
Store