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.com or env vars like process.env.API_URL
-
Authentication: Look for Authorization headers, 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