Skip to main content
Search...

client methods

Detailed reference for every Duck Query client method. Covers get, post, put, patch, del, request, byMethod, and the axios instance.

Overview

When you create a client with createDuckQueryClient<Routes>(), you get an object with eight members. Each method is generic over your route map, so TypeScript enforces valid paths, request shapes, and response types at compile time.

import { createDuckQueryClient } from '@gentleduck/query'
import type { ApiRoutes } from '@gentleduck/gen/nestjs'
 
const client = createDuckQueryClient<ApiRoutes>({
  baseURL: 'http://localhost:3000',
})

createDuckQueryClient

The factory function that creates a typed client.

function createDuckQueryClient<Routes>(
  options?: AxiosInstance | AxiosRequestConfig,
): DuckQueryClient<Routes>

Parameters:

ParameterTypeDescription
optionsAxiosInstance or AxiosRequestConfig or undefinedPass an existing Axios instance, or a config object to create a new one.

Examples:

// With config (most common)
const client = createDuckQueryClient<ApiRoutes>({
  baseURL: 'http://localhost:3000',
  withCredentials: true,
  timeout: 10000,
})
 
// With existing Axios instance
import axios from 'axios'
 
const axiosInstance = axios.create({
  baseURL: 'http://localhost:3000',
  headers: { 'X-App': 'my-app' },
})
 
const client = createDuckQueryClient<ApiRoutes>(axiosInstance)
 
// With no config (useful when baseURL is set via interceptor)
const client = createDuckQueryClient<ApiRoutes>()

get

Sends a GET request. The body is never sent, even if the request object has one.

client.get<P extends PathsByMethod<Routes, 'GET'>>(
  path: P,
  req?: RouteReqMethod<Routes, P, 'GET'>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'GET'>>>

TypeScript constraint: The path must be a route that supports the GET method. You cannot client.get('/api/auth/signin') if that route only allows POST.

Examples:

// Simple GET
const { data: users } = await client.get('/api/users')
 
// GET with query parameters
const { data: users } = await client.get('/api/users', {
  query: { page: 1, limit: 20, sort: 'name' },
})
 
// GET with path params
const { data: user } = await client.get('/api/users/:id', {
  params: { id: 'u_123' },
})
 
// GET with path params + query + headers
const { data: user } = await client.get('/api/users/:id', {
  params: { id: 'u_123' },
  query: { include: 'profile' },
  headers: { authorization: 'Bearer tok_abc' },
})
 
// GET with extra Axios config
const { data, headers } = await client.get('/api/users', undefined, {
  responseType: 'json',
  timeout: 5000,
})

post

Sends a POST request with a JSON body.

client.post<P extends PathsByMethod<Routes, 'POST'>>(
  path: P,
  req: RouteReqMethod<Routes, P, 'POST'>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'POST'>>>

Note: The req parameter is required for POST (not optional like GET).

Examples:

// POST with body
const { data: session } = await client.post('/api/auth/signin', {
  body: {
    username: 'duck',
    password: '123456',
  },
})
 
// POST with body + extra config
const { data } = await client.post(
  '/api/auth/signin',
  {
    body: { username: 'duck', password: '123456' },
  },
  {
    withCredentials: true,
    timeout: 10000,
  },
)
 
// POST with body + query params
const { data } = await client.post('/api/files/upload', {
  body: { name: 'report.pdf', size: 1024 },
  query: { overwrite: true },
})

put

Sends a PUT request with a JSON body. Typically used for full resource updates.

client.put<P extends PathsByMethod<Routes, 'PUT'>>(
  path: P,
  req: RouteReqMethod<Routes, P, 'PUT'>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'PUT'>>>

Examples:

const { data: updated } = await client.put('/api/users/:id', {
  params: { id: 'u_123' },
  body: {
    name: 'Updated Duck',
    email: 'duck@example.com',
  },
})

patch

Sends a PATCH request with a JSON body. Typically used for partial updates.

client.patch<P extends PathsByMethod<Routes, 'PATCH'>>(
  path: P,
  req: RouteReqMethod<Routes, P, 'PATCH'>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'PATCH'>>>

Examples:

const { data: updated } = await client.patch('/api/users/:id', {
  params: { id: 'u_123' },
  body: {
    name: 'Patched Duck',  // only update the name
  },
})

del

Sends a DELETE request. The body is not sent.

client.del<P extends PathsByMethod<Routes, 'DELETE'>>(
  path: P,
  req?: RouteReqMethod<Routes, P, 'DELETE'>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'DELETE'>>>

Note: The method is called del (not delete) because delete is a reserved word in JavaScript.

Examples:

// Simple delete
await client.del('/api/users/:id', {
  params: { id: 'u_123' },
})
 
// Delete with headers
await client.del('/api/users/:id', {
  params: { id: 'u_123' },
  headers: { authorization: 'Bearer tok_abc' },
})

request

A generic method that uses config.method to determine the HTTP method. Defaults to GET if no method is specified.

client.request<P extends RoutePath<Routes>>(
  path: P,
  req?: RouteReq<Routes, P>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteRes<Routes, P>>>

Examples:

// Defaults to GET
const { data } = await client.request('/api/users')
 
// Explicit method via config
const { data } = await client.request(
  '/api/auth/signin',
  { body: { username: 'duck', password: '123456' } },
  { method: 'POST' },
)

Use request when you want to determine the method dynamically. For static calls, prefer the named methods (get, post, etc.) for better type safety.

byMethod

Explicitly specify the HTTP method as the first argument. This gives you the strongest type checking: the path is constrained to routes that support the given method.

client.byMethod<M extends RouteMethods<Routes>, P extends PathsByMethod<Routes, M>>(
  method: M,
  path: P,
  req?: RouteReqMethod<Routes, P, M>,
  config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, M>>>

Examples:

const { data } = await client.byMethod('POST', '/api/auth/signin', {
  body: { username: 'duck', password: '123456' },
})
 
const { data } = await client.byMethod('GET', '/api/users/:id', {
  params: { id: 'u_123' },
})

Use byMethod when you have the method as a variable:

async function call(method: 'GET' | 'POST', path: string) {
  return client.byMethod(method, path as any)
}

axios

The underlying Axios instance. Use it to configure interceptors, defaults, or make untyped requests when needed.

// Add a request interceptor
client.axios.interceptors.request.use((config) => {
  const token = getToken()
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})
 
// Add a response interceptor
client.axios.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      redirectToLogin()
    }
    return Promise.reject(error)
  },
)
 
// Access defaults
client.axios.defaults.timeout = 10000

Config merging behavior

When both the req object and the config parameter provide the same field, the req object takes precedence:

FieldComes fromFallback
params (query string)req.queryconfig.params
headersreq.headersconfig.headers
data (body)req.bodyNot applicable
// req.query wins over config.params
await client.get('/api/users', {
  query: { page: 2 },       // this is used
}, {
  params: { page: 1 },      // this is ignored
})

Next steps