Guide

Basic Usage

Learn how to make HTTP requests with @outloud/reqo.

The library provides a simple and intuitive API for making HTTP requests. You can use the default reqo instance or create custom clients for different configurations, see more in the Instances.

Making Requests

import { reqo } from '@outloud/reqo'

// Get response with data
const response = await reqo.request({
  url: 'https://api.example.com/users',
  params: { limit: 10 }
})
console.log(response.data)

// Using method shortcuts
const users = await reqo.get('https://api.example.com/users', { limit: 10 })
console.log(users.data)

// Get data directly using $ prefix
const usersData = await reqo.$get('/users')
console.log(usersData)

// Type the response
interface User {
  id: number
  name: string
  email: string
}

const typedData = await reqo.$get<User[]>('/users')
console.log(typedData) // User[]

Options

All request methods accept an options object:

const data = await reqo.request({
  // Request URL
  url: 'https://api.example.com/data',

  // Query parameters
  params: { q: 'search' },

  // HTTP method
  method: 'POST',

  // Custom headers
  headers: {
    'Authorization': 'Bearer token',
    'X-Custom-Header': 'value'
  },

  // Request body
  data: { key: 'value' },

  // Timeout in milliseconds
  timeout: 5000,
  
  // Retry configuration
  retry: {
    limit: 3,
    delay: (count) => count * 1000
  },
  
  // Response type
  responseType: 'json',
  
  // Abort signal for cancellation
  signal: controller.signal,
  
  // Redirect behavior
  redirect: 'follow',
  
  // Credentials
  credentials: 'include',
  
  // Mode
  mode: 'cors'
})

Method shortcuts

All HTTP methods have corresponding shortcuts and $ variants for direct data access.

GET

GET accepts parameters in following order: url, params, options:

const response = await reqo.get('/endpoint', { param1: 'value1' }, {
  timeout: 3000
})

// data is the same as response.data
const data = await reqo.$get('/endpoint', { param1: 'value1' }, {
  timeout: 3000
})

HEAD accepts parameters in following order: url, params, options:

const response = await reqo.head('/endpoint')

// $ variant
const data = await reqo.$head('/endpoint')

POST

POST accepts parameters in following order: url, data, options:

const response = await reqo.post('/endpoint', { key: 'value' })

// $ variant
const data = await reqo.$post('/endpoint', { key: 'value' })

PUT

PUT accepts parameters in following order: url, data, options:

const response = await reqo.put('/endpoint', { key: 'value' })

// $ variant
const data = await reqo.$put('/endpoint', { key: 'value' })

PATCH

PATCH accepts parameters in following order: url, data, options:

const response = await reqo.patch('/endpoint', { key: 'value' })

// $ variant
const data = await reqo.$patch('/endpoint', { key: 'value' })

DELETE

DELETE accepts parameters in following order: url, options:

const response = await reqo.delete('/endpoint')

// $ variant
const data = await reqo.$delete('/endpoint')

Query Parameters

Use params option to send query parameters, it will be serialized automatically.

// Single values
const users = await reqo.$get('/users', { id: 123 })
// GET /users?id=123

// Arrays
const filtered = await reqo.$get('/users', { 
  id: [1, 2, 3],
  status: 'active'
})
// GET /users?id=1&id=2&id=3&status=active

// Nested objects
const results = await reqo.$get('/search', {
  filters: { name: 'John', age: 30 }
})
// GET /search?filters[name]=John&filters[age]=30

// POST with query params
const user = await reqo.$post('/users', { name: 'Jane' }, {
  params: { ref: 'newsletter' }
})
// POST /users?ref=newsletter

Request Body

POST, PUT, and PATCH requests accept data as the second argument or you can use data option in request method.

Different body types are automatically detected and serialized.
// JSON body (automatically serialized)
await reqo.$post('/users', {
  name: 'John',
  email: 'john@example.com'
})

// FormData
const formData = new FormData()
formData.append('file', file)
formData.append('name', 'Document')
await reqo.$post('/upload', formData)

// URL-encoded (set content-type header)
const params = new URLSearchParams()
params.append('key', 'value')
await reqo.$post('/form', params, {
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})

Response Type

The response body is automatically parsed based on the Content-Type header by default. You can also explicitly set the expected response type using the responseType option.

// Automatically parsed as JSON if Content-Type is application/json, otherwise returns text, this is default behavior
const data = await reqo.$get('/data', {}, { 
  responseType: 'auto'
})

// JSON
const json = await reqo.$get('/data', {}, { 
  responseType: 'json' 
})
console.log(json) // JSON object

// Plain text
const text = await reqo.$get('/file.txt', {}, { 
  responseType: 'text' 
})
console.log(text) // string

// Binary data
const blob = await reqo.$get('/image.png', {}, { 
  responseType: 'blob' 
})
console.log(blob) // Blob

// Array buffer
const buffer = await reqo.$get('/binary', {}, { 
  responseType: 'arrayBuffer' 
})
console.log(buffer) // ArrayBuffer

// No parsing
const response = await reqo.get('/endpoint', {}, { 
  responseType: false 
})
// Process body manually
const body = await response.body
const reader = body.getReader()

Working with Responses

When using methods without $ prefix, you get the full Response object.

const response = await reqo.get('/users')

// Access response data
console.log(response.data)

// Response metadata
console.log(response.status)
console.log(response.statusText)
console.log(response.headers)
console.log(response.ok)

// Check content type
const contentType = response.headers.get('content-type')

The $ prefix methods return only the data:

// Returns the data directly
const users = await reqo.$get('/users')

// Equivalent to:
const response = await reqo.get('/users')
const users = response.data

Timeouts

Set request timeouts to prevent hanging requests.

The default timeout is 60 seconds. You can change it globally in the client options or per request using the timeout option.
try {
  // 5 second timeout
  const data = await reqo.$get('/slow-endpoint', {}, {
    timeout: 5000
  })
} catch (error) {
  if (reqo.isError(error) && error.code === 'E_TIMEOUT') {
    console.log('Request timed out')
  }
}

Cancellation

Requests return a cancellable Future (extended Promise). You can cancel requests using the Future's cancel method or an AbortController.

Cancel method

// Start request
const request = reqo.get('/users')

// Cancel after 1 second
setTimeout(() => request.cancel(), 1000)

try {
  const data = await promise
} catch (error) {
  if (reqo.isError(error) && error.code === 'E_CANCELED') {
    console.log('Request was canceled')
  }
}

AbortController

// Or using AbortController
const controller = new AbortController()

// Start request
const promise = reqo.$get('/users', {}, {
  signal: controller.signal
})

// Cancel after 1 second
setTimeout(() => controller.abort(), 1000)

try {
  const data = await promise
} catch (error) {
  if (reqo.isError(error) && error.code === 'E_CANCELED') {
    console.log('Request was canceled')
  }
}

Error Handling

All errors returned by the library are instances of RequestError, which provides useful properties for debugging and logging.

import { reqo, errors } from '@outloud/reqo'

try {
  const data = await reqo.$get('/users/999')
} catch (error) {
  // Type-safe error checking
  if (reqo.isError(error)) {
    console.log('Status:', error.status)
    console.log('Code:', error.code)
    console.log('Message:', error.message)
    console.log('Response data:', error.data)
    console.log('URL:', error.url)
    console.log('Method:', error.method)
    console.log('Config:', error.config)
    console.log('Request:', error.request)
    console.log('Response:', error.response)

    console.log(error.toJSON()) // Serialize error to JSON, useful for logging
  }
  
  // Check specific error types
  if (error instanceof errors.TimeoutError) {
    console.log('Request timed out')
  }
  
  if (error instanceof errors.CanceledError) {
    console.log('Request was canceled')
  }
}

Type Safety

The library provides full TypeScript support:

interface User {
  id: number
  name: string
  email: string
}

interface CreateUserDto {
  name: string
  email: string
}

// Type the response
const user = await reqo.$get<User>('/users/1')
console.log(user) // User

// Type request and response
const newUser = await reqo.$post<User, CreateUserDto>('/users', {
  name: 'John',
  email: 'john@example.com'
})
console.log(newUser) // User