Define how the app talks to its API with @data-client/rest, including the shape of the data each call returns. Use it when adding or changing any API call or its data, changing how requests are sent or responses are read, or deciding how fetched data is stored, updated and kept fresh. Covers resource, RestEndpoint and pagination.
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
@data-client/rest for Resource ModelingThis 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.
Every resource() and RestEndpoint takes a schema that normalizes its response into the shared cache. Apply the
skill "data-client-schema" before defining or changing one: Entity, Collection, Union and Query patterns and references
live there, not here.
resource())path: path‑to‑regexp template (typed!)schema: Declarative data shape for a single item (typically Entity or Union)urlPrefix: Host root, if not /searchParams: Type for query parameters (TS generic) in MyResource.getListpaginationField: Add MyResource.getList.getPage for paginationoptimistic: Boolean, when true all mutations will update optimistically, improving performancebody: Type for body parameter to MyResource.getList.push, MyResource.getList.unshift, MyResource.update, MyResource.partialUpdateimport { 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,
});// GET https://jsonplaceholder.typicode.com/todos/5
const todo = useSuspense(TodoResource.get, { id: 5 });
// GET https://jsonplaceholder.typicode.com/todos
const todoList = useSuspense(TodoResource.getList);
// GET https://jsonplaceholder.typicode.com/todos?userId=1
const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 });const ctrl = useController();
// PUT https://jsonplaceholder.typicode.com/todos/5
const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo);
// PATCH https://jsonplaceholder.typicode.com/todos/5
const partialUpdateTodo = todo =>
ctrl.fetch(TodoResource.partialUpdate, { id }, todo);
// POST https://jsonplaceholder.typicode.com/todos
const addTodoToStart = todo =>
ctrl.fetch(TodoResource.getList.unshift, todo);
// POST https://jsonplaceholder.typicode.com/todos?userId=1
const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo);
// PATCH https://jsonplaceholder.typicode.com/todos/5
const toggleStatus = (completed: boolean) => ctrl.fetch(TodoResource.getList.move, { id }, { completed });
// DELETE https://jsonplaceholder.typicode.com/todos/5
const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id });
// GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2
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".
/** Stand‑alone endpoint with custom typing */
export const getTicker = new RestEndpoint({
urlPrefix: 'https://api.exchange.coinbase.com',
path: '/products/:product_id/ticker',
schema: Ticker,
pollFrequency: 2000,
});pollFrequency polls only while subscribed (useLive/useSubscription). Subscribers to the same
endpoint and args share one poll at the fastest frequency, so a faster .extend({ pollFrequency }) on
one view speeds up the others too.
Typing tips
path path‑to‑regexp template for 1st argmethod ≠ GET ⇒ 2nd arg = body (unless body: undefined)searchParams / body values purely for type inferenceRestGenerics when inheriting from RestEndpointgetOptimisticResponse()getOptimisticResponse(snap, { id }) {
const article = snap.get(Article, { id });
if (!article) throw snap.abort;
return {
id,
votes: article.votes + 1,
};
}Return each Entity's primary key plus changed fields; omitted fields keep their cached values. Without the
key the UI waits for the server (dev logs Optimistic update skipped). If args lack it (PATCH /users/profile),
take user from snap.getResponse(getMe).data, throw snap.abort if missing, and return { id: user.id, ...body }.
fetch() runs fetchResponse(url(), getRequestInit()) → parseResponse() → process(). Override the
step that owns the change, never fetch itself (that replaces the whole lifecycle):
| To change | Override |
|---|---|
| Headers (auth) | getHeaders(headers) |
| Other request options or body encoding | async getRequestInit(body), spreading await super.getRequestInit(body) |
| Query string format | searchToString(searchParams) |
| The network call (retries, another client) | fetchResponse(input, init) |
| Non-JSON responses | content option ('blob', 'text', …), else parseResponse(response) |
| Shape of the parsed data | process(value, ...args) |
Use .extend() to add or override endpoints.
export const IssueResource = resource({
// ...base config...
}).extend((Base) => ({
search: Base.getList.extend({
path: '/search/issues',
// ...custom schema or params...
}),
}));RestEndpoint over resource() for defining single endpoints or when mutation endpoints don't existresource() when mutation endpoints are not used or neededVue projects: read <name>.vue.md instead of <name>.md when it exists.
For detailed API documentation, see the references directory:
Guides (refer when user asks about these topics):
Concepts (refer when user asks about these topics):
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.
reactive/data-client
e1ea9ce
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.