data-client-rest-setup
SKILL.md
REST Protocol Setup
This skill configures @data-client/rest for a project. It should be applied after skill "data-client-setup" detects REST API patterns.
First, apply the skill "data-client-rest" for accurate implementation patterns.
Installation
Install the REST package alongside the core package:
# npm
npm install @data-client/rest
# yarn
yarn add @data-client/rest
# pnpm
pnpm add @data-client/rest
Custom RestEndpoint Base Class
After installing, offer to create a custom RestEndpoint class for the project.
Detection Checklist
Scan the existing codebase for common REST patterns to include:
- Base URL / API prefix: Look for hardcoded URLs like
https://api.example.comor env vars likeprocess.env.API_URL - Authentication: Look for
Authorizationheaders, tokens in localStorage/cookies, auth interceptors - Content-Type handling: Check if API uses JSON, form-data, or custom content types
- Error handling: Look for error response patterns, status code handling
- Request/Response transforms: Data transformations, date parsing, case conversion
- Query string format: Simple params vs nested objects (may need qs library)
Base Class Template
Create a file at src/api/BaseEndpoint.ts (or similar location based on project structure):
import { RestEndpoint, RestGenerics } from '@data-client/rest';
/**
* Base RestEndpoint with project-specific defaults.
* Extend this for all REST API endpoints.
*/
export class BaseEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
// API base URL - adjust based on detected patterns
urlPrefix = process.env.REACT_APP_API_URL ?? 'https://api.example.com';
// Add authentication headers
getHeaders(headers: HeadersInit): HeadersInit {
const token = localStorage.getItem('authToken');
return {
...headers,
...(token && { Authorization: `Bearer ${token}` }),
};
}
}
Common Lifecycle Overrides
Include these based on what's detected in the codebase. See RestEndpoint for full API documentation.
Authentication (async token refresh)
async getHeaders(headers: HeadersInit): Promise<HeadersInit> {
const token = await getValidToken(); // handles refresh
return {
...headers,
Authorization: `Bearer ${token}`,
};
}
Custom Request Init (CSRF, credentials)
getRequestInit(body?: RequestInit['body'] | Record<string, unknown>): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'include', // for cookies
headers: {
'X-CSRF-Token': getCsrfToken(),
},
};
}
Custom Response Parsing (unwrap data envelope)
process(value: any, ...args: any[]) {
// If API wraps responses in { data: ... }
return value.data ?? value;
}
Custom Error Handling
async fetchResponse(input: RequestInfo, init: RequestInit): Promise<Response> {
const response = await super.fetchResponse(input, init);
// Handle specific status codes
if (response.status === 401) {
// Trigger logout or token refresh
window.dispatchEvent(new CustomEvent('auth:expired'));
}
return response;
}
Custom Search Params (using qs library)
searchToString(searchParams: Record<string, any>): string {
// For complex nested query params
return qs.stringify(searchParams, { arrayFormat: 'brackets' });
}
Custom parseResponse (handle non-JSON)
async parseResponse(response: Response): Promise<any> {
const contentType = response.headers.get('content-type');
if (contentType?.includes('text/csv')) {
return parseCSV(await response.text());
}
return super.parseResponse(response);
}
Full Example with Multiple Overrides
import { RestEndpoint, RestGenerics } from '@data-client/rest';
import qs from 'qs';
export class BaseEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
urlPrefix = process.env.API_URL ?? 'http://localhost:3001/api';
async getHeaders(headers: HeadersInit): Promise<HeadersInit> {
const token = await getAuthToken();
return {
...headers,
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
};
}
getRequestInit(body?: RequestInit['body'] | Record<string, unknown>): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'include',
};
}
searchToString(searchParams: Record<string, any>): string {
return qs.stringify(searchParams, { arrayFormat: 'brackets' });
}
process(value: any, ...args: any[]) {
// Unwrap { data: ... } envelope if present
return value?.data ?? value;
}
}
// Helper function - implement based on project auth pattern
async function getAuthToken(): Promise<string | null> {
// Check for valid token, refresh if needed
return localStorage.getItem('token');
}
Usage After Setup
Once the base class is created, use it instead of RestEndpoint directly:
import { BaseEndpoint } from './BaseEndpoint';
import { Todo } from '../schemas/Todo';
export const getTodo = new BaseEndpoint({
path: '/todos/:id',
schema: Todo,
});
export const updateTodo = getTodo.extend({ method: 'PUT' });
Or with resource():
import { resource } from '@data-client/rest';
import { BaseEndpoint } from './BaseEndpoint';
import { Todo } from '../schemas/Todo';
export const TodoResource = resource({
path: '/todos/:id',
schema: Todo,
Endpoint: BaseEndpoint,
});
Next Steps
- Apply skill "data-client-schema" to define Entity classes
- Apply skill "data-client-rest" for resource and endpoint patterns
- Apply skill "data-client-react" or "data-client-vue" for usage
References
- RestEndpoint - Full RestEndpoint API
- resource - Resource factory function
- Authentication Guide - Auth patterns and examples
- Django Integration - Django REST Framework patterns
Weekly Installs
26
Repository
reactive/data-clientGitHub Stars
2.0K
First Seen
Feb 16, 2026
Security Audits
Installed on
opencode26
gemini-cli26
github-copilot26
codex26
amp26
kimi-cli26