Skip to content

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-client

Quick 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()