@data-client/rest
Version:
Quickly define typed REST resources and endpoints
240 lines (196 loc) โข 8.67 kB
Markdown
# TypeScript HTTP definitions
[](https://circleci.com/gh/reactive/data-client)
[](https://app.codecov.io/gh/reactive/data-client?branch=master)
[](https://www.npmjs.com/package/@data-client/rest)
[](https://bundlephobia.com/result?p=@data-client/rest)
[](https://www.npmjs.com/package/@data-client/rest)
[](http://makeapullrequest.com)
[](https://discord.gg/35nb8Mz)
<div align="center">
**[๐Read The Docs](https://dataclient.io/rest)** | [๐ฎTodo Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/todo-app?file=src%2Fresources%2FTodoResource.ts) | [๐ฎGithub Demo](https://stackblitz.com/github/reactive/data-client/tree/master/examples/github-app?file=src%2Fresources%2FIssue.tsx)
</div>
## RestEndpoint
Simplify TypeScript fetch functions with [RestEndpoint](https://dataclient.io/rest/api/RestEndpoint)
```typescript
const getTodo = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
});
```
[RestEndpoint](https://dataclient.io/rest/api/RestEndpoint) infers [path-to-regexp](https://github.com/pillarjs/path-to-regexp#compile-reverse-path-to-regexp)
argument types, enabling enforcement of function calls
```typescript
// signature requires id!
const todo = await getTodo({ id: 5 });
```
It automatically handles REST concepts like JSON serialization, consolidated error handling and more.
## Resources
Simplify related CRUD endpoints with [Resources](https://dataclient.io/rest/api/resource)
[Resources](https://dataclient.io/rest/api/resource) are a collection of `methods` for a given `data model`.
[Entities](https://dataclient.io/rest/api/Entity) and [Schemas](https://dataclient.io/concepts/normalization) declaratively define the _data model_.
[RestEndpoints](https://dataclient.io/rest/api/RestEndpoint) are the [_methods_](<https://en.wikipedia.org/wiki/Method_(computer_programming)>) on
that data.
```typescript
class Todo extends Entity {
id = 0;
userId = 0;
title = '';
completed = false;
}
const TodoResource = resource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
searchParams: {} as { userId?: string | number },
schema: Todo,
paginationField: 'page',
});
```
One Resource defines [seven endpoints](https://dataclient.io/rest/api/resource#members):
```typescript
// GET https://jsonplaceholder.typicode.com/todos/5
let todo5 = await TodoResource.get({ id: 5 });
// GET https://jsonplaceholder.typicode.com/todos
const todoList = await TodoResource.getList();
// GET https://jsonplaceholder.typicode.com/todos?userId=1
const userOneTodos = await TodoResource.getList({ userId: 1 });
// POST https://jsonplaceholder.typicode.com/todos
const newTodo = await TodoResource.getList.push({ title: 'my todo' });
// POST https://jsonplaceholder.typicode.com/todos?userId=1
const newUserOneTodo = await TodoResource.getList.push(
{ userId: 1 },
{ title: 'my todo' },
);
// GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2
const nextPageOfTodos = await TodoResource.getList.getPage({
userId: 1,
page: 2,
});
// PUT https://jsonplaceholder.typicode.com/todos/5
todo5 = await TodoResource.update({ id: 5 }, { title: 'my todo' });
// PATCH https://jsonplaceholder.typicode.com/todos/5
todo5 = await TodoResource.partialUpdate({ id: 5 }, { title: 'my todo' });
// DELETE https://jsonplaceholder.typicode.com/todos/5
await TodoResource.delete({ id: 5 });
```
## Free React Integration
No need for any custom hooks. All endpoints are 100% compatible with [Reactive Data Client](https://dataclient.io)
### [Rendering](https://dataclient.io/docs/getting-started/data-dependency)
```typescript
const todo = useSuspense(TodoResource.get, { id: 5 });
const todoList = useSuspense(TodoResource.getList);
```
### [Mutations](https://dataclient.io/docs/getting-started/mutations)
```typescript
const ctrl = useController();
const updateTodo = data => ctrl.fetch(TodoResource.update, { id }, data);
const partialUpdateTodo = data =>
ctrl.fetch(TodoResource.partialUpdate, { id }, data);
const addTodoToEnd = data => ctrl.fetch(TodoResource.getList.push, data);
const addTodoToBeginning = data =>
ctrl.fetch(TodoResource.getList.unshift, data);
const deleteTodo = data => ctrl.fetch(TodoResource.delete, { id });
```
### [Programmatic queries](https://dataclient.io/rest/api/Query)
```tsx
const queryRemainingTodos = new schema.Query(
TodoResource.getList.schema,
entries => entries.filter(todo => !todo.completed).length,
);
const allRemainingTodos = useQuery(queryRemainingTodos);
const firstUserRemainingTodos = useQuery(queryRemainingTodos, { userId: 1 });
```
```typescript
const groupTodoByUser = new schema.Query(
TodoResource.getList.schema,
todos => Object.groupBy(todos, todo => todo.userId),
);
const todosByUser = useQuery(groupTodoByUser);
```
### TypeScript requirements
TypeScript is optional, but will only work with 4.0 or above. 4.1 is needed for stronger types as it
supports inferring argument types from the path templates.
### Prior Art
- [Backbone Model](https://backbonejs.org/#Model)
- [ImmutableJS Record](https://immutable-js.github.io/immutable-js/docs/#/Record)
## API
#### Networking definition
- [Endpoints](https://dataclient.io/rest/api/Endpoint): [RestEndpoint](https://dataclient.io/rest/api/RestEndpoint)
- [Resources](https://dataclient.io/docs/getting-started/resource): [resource()](https://dataclient.io/rest/api/resource), [hookifyResource()](https://dataclient.io/rest/api/hookifyResource)
<table>
<caption>
<a href="https://dataclient.io/docs/concepts/normalization">Data model</a>
</caption>
<thead>
<tr>
<th>Data Type</th>
<th>Mutable</th>
<th>Schema</th>
<th>Description</th>
<th><a href="https://dataclient.io/rest/api/schema#queryable">Queryable</a></th>
</tr>
</thead>
<tbody><tr>
<td rowSpan="4"><a href="https://en.wikipedia.org/wiki/Object_(computer_science)">Object</a></td>
<td align="center">โ
</td>
<td><a href="https://dataclient.io/rest/api/Entity">Entity</a>, <a href="https://dataclient.io/rest/api/EntityMixin">EntityMixin</a></td>
<td>single <em>unique</em> object</td>
<td align="center">โ
</td>
</tr>
<tr>
<td align="center">โ
</td>
<td><a href="https://dataclient.io/rest/api/Union">Union(Entity)</a></td>
<td>polymorphic objects (<code>A | B</code>)</td>
<td align="center">โ
</td>
</tr>
<tr>
<td align="center">๐</td>
<td><a href="https://dataclient.io/rest/api/Object">Object</a></td>
<td>statically known keys</td>
<td align="center">๐</td>
</tr>
<tr>
<td align="center"></td>
<td><a href="https://dataclient.io/rest/api/Invalidate">Invalidate(Entity)</a></td>
<td><a href="https://dataclient.io/docs/concepts/expiry-policy#invalidate-entity">delete an entity</a></td>
<td align="center">๐</td>
</tr>
<tr>
<td rowSpan="3"><a href="https://en.wikipedia.org/wiki/List_(abstract_data_type)">List</a></td>
<td align="center">โ
</td>
<td><a href="https://dataclient.io/rest/api/Collection">Collection(Array)</a></td>
<td>growable lists</td>
<td align="center">โ
</td>
</tr>
<tr>
<td align="center">๐</td>
<td><a href="https://dataclient.io/rest/api/Array">Array</a></td>
<td>immutable lists</td>
<td align="center">๐</td>
</tr>
<tr>
<td align="center"> </td>
<td><a href="https://dataclient.io/rest/api/All">All</a></td>
<td>list of all entities of a kind</td>
<td align="center">โ
</td>
</tr>
<tr>
<td rowSpan="2"><a href="https://en.wikipedia.org/wiki/Associative_array">Map</a></td>
<td align="center">โ
</td>
<td><a href="https://dataclient.io/rest/api/Collection">Collection(Values)</a></td>
<td>growable maps</td>
<td align="center">โ
</td>
</tr>
<tr>
<td align="center">๐</td>
<td><a href="https://dataclient.io/rest/api/Values">Values</a></td>
<td>immutable maps</td>
<td align="center">๐</td>
</tr>
<tr>
<td>any</td>
<td align="center"></td>
<td><a href="https://dataclient.io/rest/api/Query">Query(Queryable)</a></td>
<td>memoized custom transforms</td>
<td align="center">โ
</td>
</tr>
</tbody></table>