Basic Usage
Defining Endpoints
Use the HTTP method helpers to define endpoints. All helpers share the same signature:
get<TInput, TOutput, TError>(path, handler?, hooks?)
post<TInput, TOutput, TError>(path, handler?, hooks?)
put<TInput, TOutput, TError>(path, handler?, hooks?)
patch<TInput, TOutput, TError>(path, handler?, hooks?)
del<TInput, TOutput, TError>(path, handler?, hooks?)path is a string or a function that receives input and returns a string:
import { createApiClient, get, post, patch, del } from 'endpoint-fetcher';
type User = { id: string; name: string; email: string };
type CreateUserInput = { name: string; email: string };
type UpdateUserInput = Partial<User> & { id: string };
const api = createApiClient({
listUsers: get<void, User[]>('/users'),
getUser: get<{ id: string }, User>((input) => `/users/${input.id}`),
createUser: post<CreateUserInput, User>('/users'),
updateUser: patch<UpdateUserInput, User>((input) => `/users/${input.id}`),
deleteUser: del<{ id: string }, void>((input) => `/users/${input.id}`),
}, {
baseUrl: 'https://api.example.com',
});Query Parameters
Build query strings in the path function:
const searchUsers = get<{ query: string; page?: number }, User[]>((input) => {
const params = new URLSearchParams({ q: input.query });
if (input.page) params.set('page', input.page.toString());
return `/users/search?${params}`;
});The endpoint() Helper
For full control over endpoint configuration:
import { endpoint } from 'endpoint-fetcher';
const searchUsers = endpoint<SearchInput, User[], ApiError>({
method: 'POST',
path: '/users/search',
handler: async ({ input, fetch, baseUrl, path }) => {
const response = await fetch(`${baseUrl}${path}`, {
method: 'POST',
body: JSON.stringify(input),
});
return response.json();
},
hooks: {
beforeRequest: async (url, init) => {
console.log('searching users');
return { url, init };
},
},
});Client Configuration
const api = createApiClient(endpoints, {
baseUrl: 'https://api.example.com', // required
defaultHeaders: { 'X-API-Key': 'key' }, // optional
fetch: customFetch, // optional - custom fetch instance
hooks: { /* ... */ }, // optional - global hooks
plugins: [ /* ... */ ], // optional - plugins
});Making Requests
const users = await api.listUsers(); // void input
const user = await api.getUser({ id: '123' }); // with input
const newUser = await api.createUser({ name: 'Jane', email: 'jane@example.com' });TypeScript enforces input types and infers return types throughout.
Error Handling
When a request fails (non-2xx status), endpoint-fetcher throws:
{
status: number; // HTTP status code
statusText: string; // HTTP status text
error: TError; // Parsed error response body
}Use TError (the third generic) to type the error body:
type ApiError = { message: string; code: string };
const api = createApiClient({
createUser: post<CreateUserInput, User, ApiError>('/users'),
}, { baseUrl: 'https://api.example.com' });
try {
await api.createUser({ name: 'Jane', email: 'bad-email' });
} catch (err: unknown) {
const error = err as { status: number; statusText: string; error: ApiError };
console.log(error.status); // 400
console.log(error.error.code); // 'VALIDATION_ERROR'
console.log(error.error.message); // 'Invalid email'
}TypeScript doesn’t enforce types in
catchblocks — use type assertions to accessTErrorproperties.