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:
const app = router().merge('/v1', v1);
const handler = createKaitoHTTPHandler({
router: app,
// ...
});
export type App = typeof app;
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 originalRequest
object.response
: TheResponse
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);
}
}