| name | data-client-rest |
| description | Define REST APIs with @data-client/rest - resource(), RestEndpoint, CRUD (GET/POST/PUT/PATCH/DELETE), HTTP fetch, normalize, cache, urlPrefix, path-to-regexp parameters, searchParams, pagination, extend(), auth/headers, optimistic updates, polling, file download, blob, parseResponse. Use when defining or modifying network endpoints, REST resources, or the HTTP layer. |
| license | Apache 2.0 |
Guide: Using @data-client/rest for Resource Modeling
This project uses @data-client/rest to define, fetch, normalize, and update RESTful resources and entities in React/TypeScript apps with type safety and automatic cache management.
Always follow these patterns when generating code that interacts with remote APIs.
1. Defining Schemas
This project uses schemas to define and normalize data models with type safety and automatic cache management. Apply the skill "data-client-schema" for schema patterns.
Always follow these patterns (apply the skill "data-client-schema") when generating mutable data definitions.
2. Resources (resource())
- resource() creates a collection of RestEndpoints for CRUD operations on a common object
- Required fields:
path: path‑to‑regexp template (typed!)
schema: Declarative data shape for a single item (typically Entity or Union)
- Optional:
urlPrefix: Host root, if not /
searchParams: Type for query parameters (TS generic) in MyResource.getList
paginationField: Add MyResource.getList.getPage for pagination
optimistic: Boolean, when true all mutations will update optimistically, improving performance
body: Type for body parameter to MyResource.getList.push, MyResource.getList.unshift, MyResource.update, MyResource.partialUpdate
import { Entity, resource } from '@data-client/rest';
import { User } from './User';
export class Todo extends Entity {
id = 0;
user = User.fromJS();
title = '';
completed = false;
createdAt = new Date();
static key = 'Todo';
static schema = {
user: User,
createdAt: (iso: string) => new Date(iso),
}
}
export const TodoResource = resource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
searchParams: {} as { userId?: string | number } | undefined,
paginationField: 'page',
nonFilterArgumentKeys: ['orderBy'],
optimistic: true,
});
Usage
const todo = useSuspense(TodoResource.get, { id: 5 });
const todoList = useSuspense(TodoResource.getList);
const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 });
const ctrl = useController();
const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo);
const partialUpdateTodo = todo =>
ctrl.fetch(TodoResource.partialUpdate, { id }, todo);
const addTodoToStart = todo =>
ctrl.fetch(TodoResource.getList.unshift, todo);
const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo);
const toggleStatus = (completed: boolean) => ctrl.fetch(TodoResource.getList.move, { id }, { completed });
const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id });
const getNextPage = (page) => ctrl.fetch(TodoResource.getList.getPage, { userId: 1, page })
For more detailed usage, apply the skill "data-client-react" or "data-client-vue".
export const getTicker = new RestEndpoint({
urlPrefix: 'https://api.exchange.coinbase.com',
path: '/products/:product_id/ticker',
schema: Ticker,
pollFrequency: 2000,
});
Typing tips
path path‑to‑regexp template for 1st arg
method ≠ GET ⇒ 2nd arg = body (unless body: undefined)
- Provide
searchParams / body values purely for type inference
- Use
RestGenerics when inheriting from RestEndpoint
getOptimisticResponse()
getOptimisticResponse(snap, { id }) {
const article = snap.get(Article, { id });
if (!article) throw snap.abort;
return {
id,
votes: article.votes + 1,
};
}
RestEndpoint lifecycle methods
- Perform Fetch:
fetchResponse() → parseResponse() → process()
- url(urlParams):
urlPrefix + path + (searchParams → searchToString())
- getRequestInit(body):
getHeaders() + method + signal
4. Extending Resources
Use .extend() to add or override endpoints.
export const IssueResource = resource({
}).extend((Base) => ({
search: Base.getList.extend({
path: '/search/issues',
}),
}));
5. Best Practices & Notes
- When asked to browse or navigate to a web address, actual visit the address
- Always set up
schema on every resource/entity/collection for normalization
- Prefer
RestEndpoint over resource() for defining single endpoints or when mutation endpoints don't exist
- For blob/file downloads and other non-JSON responses, see network-transform: file download.
6. Common Mistakes to Avoid
- Don't use
resource() when mutation endpoints are not used or needed
References
For detailed API documentation, see the references directory:
Guides (refer when user asks about these topics):
Concepts (refer when user asks about these topics):
- expiry-policy - Cache invalidation, stale data, dataExpiryLength, errorExpiryLength
- error-policy - Error handling, retry behavior, soft vs hard errors
ALWAYS follow these patterns and refer to the official docs for edge cases. Prioritize code generation that is idiomatic, type-safe, and leverages automatic normalization/caching via schema definitions.