API Client Overview
@vafast/api-client is a type-safe API client designed for the Vafast framework. It is built on a middleware architecture and supports Eden-style chained calls.
Core Features
- 🎯 Type safety - inferred automatically from vafast routes, or synced via the CLI
- 🧅 Middleware architecture - Koa-style onion model, flexibly composable
- 🔄 Built-in retries - exponential backoff and conditional retries
- ⏱️ Timeout control - per-request and global timeouts
- 📡 SSE support - streaming responses with automatic reconnection
- 🎨 Go-style errors - unified
{ data, error }handling
Installation
bash
npm install @vafast/api-clientQuick Start
typescript
import { createClient, eden, defineMiddleware } from '@vafast/api-client'
const tokenMiddleware = defineMiddleware(async (ctx, next) => {
const token = localStorage.getItem('token')
if (token) ctx.headers.set('Authorization', `Bearer ${token}`)
return next()
})
const client = createClient({
baseURL: 'http://localhost:3000',
timeout: 30_000,
}).use(tokenMiddleware)
const api = eden<Api>(client)
const { data, error } = await api.users.get({ page: 1 })
if (error) {
console.error(`${error.code}: ${error.message}`)
return
}
console.log(data.users)Core API
createClient(config)
typescript
// Option 1: pass only the baseURL
const client = createClient('http://localhost:3000')
.timeout(30_000)
.use(tokenMiddleware)
// Option 2: pass a config object (recommended)
const client = createClient({
baseURL: 'http://localhost:3000',
timeout: 30_000,
headers: { 'X-App-Id': 'my-app' },
}).use(tokenMiddleware)typescript
interface ClientConfig {
baseURL: string
timeout?: number // default 30000ms
headers?: Record<string, string>
}Chainable methods: .use(middleware) / .headers(headers) / .timeout(ms)
eden<T>(client)
typescript
import { createApiClient } from './api.generated' // generated by the CLI
const api = createApiClient(client)
const { data, error } = await api.users.find.post({ current: 1, pageSize: 10 })Middleware (Basics)
typescript
import { defineMiddleware, retryMiddleware, loggerMiddleware } from '@vafast/api-client'
const auth = defineMiddleware(async (ctx, next) => {
const token = localStorage.getItem('token')
if (token) ctx.headers.set('Authorization', `Bearer ${token}`)
return next()
})
const client = createClient({ baseURL: '/api' })
.use(auth)
.use(retryMiddleware({ count: 3, delay: 1000 }))
.use(loggerMiddleware({ prefix: '[API]' }))For multi-tenant app-id, token refresh queues and multi-service factories, see Advanced Usage.
Go-Style Error Handling
Every request returns { data, error }: check error first, then use data. Don't use try/catch for 4xx/5xx business failures.
typescript
import { isValidationError, mapDetailsToFormFields } from '@vafast/api-client'
const { data, error } = await api.users.get()
if (error) {
if (isValidationError(error)) {
formRef.setFields(mapDetailsToFormFields(error.details))
return
}
switch (error.code) {
case 401: redirectToLogin(); break
case 403: showPermissionDenied(); break
default: showError(error.message)
}
return
}
console.log(data.users)SSE Streaming
Chain .sse() after a regular HTTP method:
typescript
api.chat.stream.post({ messages: [{ role: 'user', content: 'Hello' }] }).sse({
onMessage: (data) => {
if (data.content) process.stdout.write(data.content)
},
onError: (error) => console.error(error),
onClose: () => console.log('done'),
})
const sub = api.events.get({ channel: 'live' }).sse({ onMessage: console.log })
sub.unsubscribe()Request Cancellation
typescript
const controller = new AbortController()
const { data, error } = await api.users.get({ page: 1 }, { signal: controller.signal })
controller.abort()Related Links
- Comparison — vs. tRPC / Eden / Hono / OpenAPI / Axios
- Basic Usage
- Advanced Usage — multi-tenancy, refresh queues, multiple services, upload orchestration
- CLI Tool
- Testing