Guide

Instances

Create and configure custom HTTP client instances.

While the default http instance works for simple use cases, you can create custom client instances with specific configurations for different APIs or use cases.

Creating a Client

Use the createClient function to create a new client instance:

import { createClient } from '@outloud/reqo'

const apiClient = createClient({
  url: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Authorization': 'Bearer your-token'
  }
})

// Use the client
const users = await apiClient.$get('/users')

Options

id
string | undefined
Descriptive name for the client, will be used in errors and debugging.
const client = createClient({ 
  id: 'github-api' 
})
url
string | undefined
Base URL for all requests. Paths will be appended to this URL.
const client = createClient({ 
  url: 'https://api.example.com/v1' 
})

// GET https://api.example.com/v1/users
await client.$get('/users')
headers
HeaderValues | undefined
Default headers to include in all requests.
const client = createClient({
  headers: {
    'Authorization': 'Bearer token',
    'X-API-Key': 'key'
  }
})
timeout
number | false
Request timeout in milliseconds. Set to false to disable timeouts.

Defaults to 60000 (60 seconds).
const client = createClient({ 
  timeout: 5000  // 5 seconds
})
retry
UserRetryOptions | boolean
Retry configuration. Set to true for default retry behavior or provide custom options.

Defaults to false.
const client = createClient({
  retry: {
    limit: 3,
    methods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'],
    statusCodes: [408, 429, 500, 502, 503, 504],
    delay: (count) => count * 1000
  }
})
redirect
RequestInit['redirect']
How to handle redirects: 'follow', 'error', or 'manual'.

Defaults to 'follow'.
const client = createClient({ 
  redirect: 'manual' 
})
fetch
typeof fetch
Custom fetch implementation. Useful for testing or using polyfills.

Defaults to globalThis.fetch.
import nodeFetch from 'node-fetch'

const client = createClient({ 
  fetch: nodeFetch as any
})
validate
ValidateFn | undefined
Custom response validation function. By default, responses with ok: false throw errors.
const client = createClient({
  validate: (response) => {
    // Only accept 200 status
    return response.status === 200
  }
})

Multiple Instances

Create different clients for different APIs:

import { createClient } from '@outloud/reqo'

// GitHub API client
const github = createClient({
  id: 'github',
  url: 'https://api.github.com',
  headers: {
    'Accept': 'application/vnd.github.v3+json',
    'Authorization': `token ${process.env.GITHUB_TOKEN}`
  },
  timeout: 5000
})

// Internal API client
const internalApi = createClient({
  id: 'internal',
  url: 'https://internal-api.company.com',
  headers: {
    'X-API-Key': process.env.INTERNAL_API_KEY
  },
  retry: {
    limit: 3,
    delay: (count) => count * 2000
  }
})

// Use the clients
const repos = await github.$get('/users/username/repos')
const data = await internalApi.$get('/data')

Base URL Resolution

The client intelligently resolves URLs:

const client = createClient({
  url: 'https://api.example.com/v1'
})

// Absolute URLs override base URL
await client.$get('https://other-api.com/data')
// GET https://other-api.com/data

// Relative paths are appended
await client.$get('/users')
// GET https://api.example.com/v1/users

await client.$get('users')
// GET https://api.example.com/v1/users

// Get url
client.getUrl('items')
// https://api.example.com/v1/items

// Browser environment - relative base URLs
const browserClient = createClient({
  url: '/api/v1'  // relative to window.location.origin
})

Accessing Configuration

Get the base URL from a client instance:

const client = createClient({
  url: 'https://api.example.com'
  headers: {
    'X-Custom-Header': 'value'
  }
})

console.log(client.baseUrl)  // 'https://api.example.com'
console.log(client.options)  // { timeout: 60000, headers: { ... }, ... }

Client Class

You can also extend the Client class for more advanced use cases:

import { Client, type ClientOptions } from '@outloud/reqo'

class ApiClient extends Client {
  constructor(options: Partial<ClientOptions> = {}) {
    super({
      url: 'https://api.example.com',
      ...options
    })
    
    // Setup hooks or custom logic
    this.on('request', (config) => {
      console.log(`Making ${config.method} request to ${config.url}`)
    })
  }
  
  // Add custom methods
  async getCurrentUser() {
    return this.$get('/me')
  }
  
  async uploadFile(file: File) {
    const formData = new FormData()
    formData.append('file', file)
    return this.$post('/upload', formData)
  }
}

const api = new ApiClient()
const user = await api.getCurrentUser()

Per-Request Overrides

Options passed to individual requests override client defaults:

const client = createClient({
  timeout: 5000,
  headers: {
    'X-Default': 'value'
  }
})

// Override timeout for this request
await client.$get('/slow-endpoint', {}, {
  timeout: 30000, // overrides client timeout
  headers: {
    'X-Default': 'overridden',  // Replaces default header
    'X-Custom': 'additional'    // Adds new header
  }
})