client methods
Detailed reference for every Duck Query client method. Covers get, post, put, patch, del, request, byMethod, and the axios instance.
Overview
createDuckQueryClient<Routes>() returns an object with eight members. Every method is
generic over the route map, so TypeScript checks 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',
})import { createDuckQueryClient } from '@gentleduck/query'
import type { ApiRoutes } from '@gentleduck/gen/nestjs'
const client = createDuckQueryClient<ApiRoutes>({
baseURL: 'http://localhost:3000',
})createDuckQueryClient
Factory that builds a typed client.
function createDuckQueryClient<Routes>(
options?: AxiosInstance | AxiosRequestConfig,
): DuckQueryClient<Routes>function createDuckQueryClient<Routes>(
options?: AxiosInstance | AxiosRequestConfig,
): DuckQueryClient<Routes>Parameters:
| Parameter | Type | Description |
|---|---|---|
options | AxiosInstance or AxiosRequestConfig or undefined | An existing Axios instance, or a config object for 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>()// 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 dropped, even if req has one.
client.get<P extends PathsByMethod<Routes, 'GET'>>(
path: P,
req?: RouteReqMethod<Routes, P, 'GET'>,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'GET'>>>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 GET-compatible route. client.get('/api/auth/signin') is a type error if the route only accepts 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,
})// 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'>>>client.post<P extends PathsByMethod<Routes, 'POST'>>(
path: P,
req: RouteReqMethod<Routes, P, 'POST'>,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'POST'>>>Note: req is required for POST (unlike 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 },
})// 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 — use 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'>>>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',
},
})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 — use 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'>>>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
},
})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 dropped.
client.del<P extends PathsByMethod<Routes, 'DELETE'>>(
path: P,
req?: RouteReqMethod<Routes, P, 'DELETE'>,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'DELETE'>>>client.del<P extends PathsByMethod<Routes, 'DELETE'>>(
path: P,
req?: RouteReqMethod<Routes, P, 'DELETE'>,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteResMethod<Routes, P, 'DELETE'>>>Note: named del 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' },
})// 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
Generic method that reads config.method for the HTTP verb. Defaults to GET.
client.request<P extends RoutePath<Routes>>(
path: P,
req?: RouteReq<Routes, P>,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<RouteRes<Routes, P>>>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' },
)// 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 the method is dynamic. For static calls, prefer get, post, and
friends for tighter types.
byMethod
Pass the HTTP method as the first argument — this gives the strongest type checking. The path is constrained to routes supporting that 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>>>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' },
})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 the method is a variable:
async function call(method: 'GET' | 'POST', path: string) {
return client.byMethod(method, path as any)
}async function call(method: 'GET' | 'POST', path: string) {
return client.byMethod(method, path as any)
}axios
The underlying Axios instance. Use it for interceptors, defaults, or untyped requests.
// 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// 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 = 10000Config merging behavior
When req and config both set the same field, req wins:
| Field | Comes from | Fallback |
|---|---|---|
params (query string) | req.query | config.params |
headers | req.headers | config.headers |
data (body) | req.body | Not applicable |
// req.query wins over config.params
await client.get('/api/users', {
query: { page: 2 }, // this is used
}, {
params: { page: 1 }, // this is ignored
})// 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
- Types reference: all exported types for building custom route maps.
- Advanced patterns: interceptors, error handling, custom routes.