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.
75
94%
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.
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.
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,
};
}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 | 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...
}),
}));schema on every resource/entity/collection for normalizationRestEndpoint 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
b475613
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.