Client

Kaito provides a strongly-typed HTTP client that seamlessly integrates with your Kaito server. The client supports all HTTP methods, streaming responses, and Server-Sent Events (SSE) out of the box.

To ensure compatibility, always use matching versions of the client and server packages, as they are released together.

bun i @kaito-http/client

Basic Usage

Create a client instance by providing your API’s type and base URL:

api/index.ts
const app = router().merge('/v1', v1);
 
const handler = createKaitoHTTPHandler({
	router: app,
	// ...
});
 
export type App = typeof app;
client/index.ts
import {createKaitoHTTPClient} from '@kaito-http/client';
import type {App} from '../api/index.ts'; // Use `import type` to avoid runtime overhead
 
const api = createKaitoHTTPClient<App>({
	base: 'http://localhost:3000',
});

Making Requests

Normal Requests

The Kaito client ensures type safety across your entire API. It automatically:

  • Validates input data (query parameters, path parameters, and request body)
  • Constructs the correct URL
  • Provides proper TypeScript types for the response
// `user` will be fully typed based on your route definition
const user = await api.get('/v1/users/:id', {
	params: {
		id: '123',
	},
});
 
console.log(user);
 
await api.post('/v1/users/@me', {
	body: {
		name: 'John Doe', // Body schema is enforced by TypeScript
	},
});

Non-JSON Responses

For endpoints that return a Response instance, you must pass response: true to the request options. This is enforced for you at a compile time type level, so you can’t accidentally forget to pass it. The option is needed so the runtime JavaScript doesn’t assume the response is JSON.

const response = await api.get('/v1/response/', {
	response: true,
});
 
const text = await response.text(); // or you could use .arrayBuffer() or .blob(), etc

Server-Sent Events (SSE)

The client provides built-in support for SSE streams. You can iterate over the events using a for await...of loop:

// GET request with SSE
const stream = await api.get('/v1/sse_stream', {
	sse: true, // sse: true is enforced at a compile time type level
	query: {
		content: 'Your streaming content',
	},
});
 
for await (const event of stream) {
	console.log('event', event.data);
}
 
// POST request with SSE
const postStream = await api.post('/v1/sse_stream', {
	sse: true,
	body: {
		count: 20,
	},
});
 
for await (const event of postStream) {
	// Handle different event types
	switch (event.event) {
		case 'numbers':
			console.log(event.data.digits); // TypeScript knows this is a number
			break;
		case 'data':
			console.log(event.data.obj); // TypeScript knows this is an object
			break;
		case 'text':
			console.log(event.data.text); // TypeScript knows this is a string
			break;
	}
}

Request Configuration

The client provides several options to customize your requests:

// Cancel requests using AbortSignal
const controller = new AbortController();
const user = await api.get('/v1/users/:id', {
	params: {id: '123'},
	signal: controller.signal,
});
 
// Add custom headers
const response = await api.post('/v1/users', {
	body: {name: 'John'},
	headers: {
		'X-Custom-Header': 'value',
	},
});

For authentication and session management, all requests include credentials by default (credentials: 'include'). This ensures cookies are properly sent with cross-origin requests.

Error Handling

When a route throws an error, the client throws a KaitoClientHTTPError with detailed information about what went wrong:

  • .request: The original Request object
  • .response: The Response object containing status code and headers
  • .body: The error response with this structure:
    {
      success: false,
      message: string,
      // Additional error details may be included
    }

Here’s how to handle errors effectively:

import {isKaitoClientHTTPError} from '@kaito-http/client';
 
try {
	const response = await api.get('/v1/this-will-throw');
} catch (error: unknown) {
	if (isKaitoClientHTTPError(error)) {
		console.log('Error message:', error.message);
		console.log('Status code:', error.response.status);
		console.log('Error details:', error.body);
	}
}