Skip to Content
✨ New: Auth Plugin. JWT, OAuth2, API Key, and more authentication strategies out of the box. Read more... 🎉
Basic Usage

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 catch blocks — use type assertions to access TError properties.