Client
The client gives you a fully typed HTTP client derived from your contracts. No code generation — just TypeScript inference.
Creating a client
import { createClient } from "contract-kit";
import { getTodo, listTodos, createTodo } from "@/app/contracts/todo";
const client = createClient({
baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
});
export const getTodoEndpoint = client.endpoint(getTodo);
export const listTodosEndpoint = client.endpoint(listTodos);
export const createTodoEndpoint = client.endpoint(createTodo);
Making requests
GET with path parameters
const todo = await getTodoEndpoint.call({
path: { id: "123" },
});
console.log(todo.title); // fully typed
GET with query parameters
const result = await listTodosEndpoint.call({
query: {
completed: true,
limit: 10,
offset: 0,
},
});
console.log(result.todos); // Todo[]
POST with a body
const newTodo = await createTodoEndpoint.call({
body: {
title: "New todo",
completed: false,
},
});
console.log(newTodo.id); // string
With custom headers
const todo = await getTodoEndpoint.call({
path: { id: "123" },
headers: {
Authorization: `Bearer ${token}`,
},
});
Error handling
The client returns the response body on success and throws a ContractError on non-2xx responses.
import { ContractError } from "contract-kit";
try {
await createTodoEndpoint.call({
body: { title: "New todo" },
});
} catch (error) {
if (error instanceof ContractError) {
console.error(error.status, error.details);
}
}
Configuration
Global headers
const client = createClient({
baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
headers: () => ({
"X-Api-Version": "1.0",
}),
});
Headers can be a function (sync or async) so you can inject tokens dynamically.
Custom fetch
const client = createClient({
baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
fetch: customFetch,
});
Type safety
The client enforces your contract at the type level. TypeScript will catch mistakes before your code runs.
// TypeScript knows exactly what's required
const todo = await getTodoEndpoint.call({
path: { id: "123" }, // error if missing or wrong type
});
// TypeScript knows the response shape
todo.title; // string
todo.completed; // boolean
// TypeScript prevents invalid usage
await getTodoEndpoint.call({
path: { id: 123 }, // type error: id must be string
});