## What is Duck Query?

Duck Query is a lightweight HTTP client built on top of Axios that consumes the route
types generated by Duck Gen. Instead of manually typing every API call, Duck Query reads
your generated route map and provides full autocomplete and type checking automatically.

## Install Duck Query

```bash
bun add @gentleduck/duck-query axios
```

Axios is a peer dependency. If it is already in your project, you only need `@gentleduck/duck-query`.

## Create a typed client

```ts title="client/api/client.ts"
import { createDuckQuery } from '@gentleduck/duck-query'
import type { ApiRoutes } from '@gentleduck/gen/nestjs'

export const api = createDuckQuery<ApiRoutes>({
  baseURL: 'http://localhost:3000',
})
```

That is the entire setup. The `api` client now knows every route, every request shape,
and every response type.

## Making typed requests

### GET requests

```ts
// List users with query parameters
const users = await api.get('/api/users', {
  query: { page: 1, limit: 10 },
})
// users.data is UserDto[]
// users.message is UserMessage
// users.success is boolean

// Get a single user
const user = await api.get('/api/users/:id', {
  params: { id: '123' },
})
// user.data is UserDto
```

### POST requests

```ts
// Create a user
const newUser = await api.post('/api/users', {
  body: {
    name: 'Alice',
    email: 'alice@example.com',
    password: 'secure123',
  },
})
// newUser.data is UserDto
```

### PUT requests

```ts
// Update a user
const updated = await api.put('/api/users/:id', {
  params: { id: '123' },
  body: {
    name: 'Alice Updated',
  },
})
```

### DELETE requests

```ts
// Delete a user
const result = await api.del('/api/users/:id', {
  params: { id: '123' },
})
// result.data is null
```

} title="Path parameters">
  Notice that Duck Query handles path parameter substitution automatically.
  When you pass `params: { id: '123' }` for the path `/api/users/:id`, Duck Query
  replaces `:id` with `123` in the actual URL.

## What you get for free

With Duck Query, TypeScript enforces:

* **Path autocomplete**: Only valid route paths are accepted.
* **Method constraints**: `api.post()` only accepts paths that support POST.
* **Request shape**: The `body`, `query`, `params`, and `headers` fields match exactly what the server expects.
* **Response types**: The return type matches exactly what the server sends back.

```ts
// TypeScript error: '/api/users' does not support DELETE
api.del('/api/users', { params: {} })

// TypeScript error: missing required field 'password'
api.post('/api/users', {
  body: { name: 'Alice', email: 'alice@example.com' },
})

// TypeScript error: '/api/nonexistent' is not a valid path
api.get('/api/nonexistent')
```

## Using with React Query

Duck Query integrates naturally with TanStack React Query:

```tsx title="client/hooks/use-users.ts"
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { api } from '../api/client'

export function useUsers(page: number = 1) {
  return useQuery({
    queryKey: ['users', page],
    queryFn: () => api.get('/api/users', { query: { page, limit: 10 } }),
  })
}

export function useCreateUser() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (body: { name: string; email: string; password: string }) =>
      api.post('/api/users', { body }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] })
    },
  })
}
```

The return types of `useQuery` and `useMutation` are fully inferred from the generated
route types. No manual type annotations needed.

## Comparing approaches

| Approach | Lines of type code | Type safety | Maintenance |
| --- | --- | --- | --- |
| Manual Axios types | ~50 per module | Partial (can drift) | High |
| Generated types + manual Axios | ~10 per module | Full | Medium |
| Duck Query | ~3 per module | Full | Low |

## Next

[Chapter 8: Real World Patterns](/duck-gen/course/real-world-patterns)