> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
> Use this file to discover all available pages before exploring further.

# Client Usage

> Practical examples of making requests and handling responses with the Hono RPC client

## Making Requests

The Hono RPC client provides a fluent API for making HTTP requests with full type safety.

### GET Requests

Simple GET requests with no parameters:

```typescript theme={null}
import { hc } from 'hono/client'

const client = hc<typeof app>('http://localhost:8787')

const res = await client.posts.$get()
const data = await res.json()
```

### GET with Query Parameters

Pass query parameters using the `query` property:

```typescript theme={null}
const app = new Hono()
  .get('/search',
    validator('query', () => ({ q: '', tag: [''], filter: '' })),
    (c) => c.json({ results: [] })
  )

const client = hc<typeof app>('http://localhost')

const res = await client.search.$get({
  query: {
    q: 'hono',
    tag: ['framework', 'typescript'],
    filter: 'recent'
  }
})
```

<Note>
  Array query parameters are automatically serialized as multiple parameters with the same key (e.g., `tag=framework&tag=typescript`).
</Note>

### POST Requests with JSON

Send JSON data using the `json` property:

```typescript theme={null}
const app = new Hono()
  .post('/posts',
    validator('json', () => ({ title: '', content: '' })),
    (c) => c.json({ id: 123, title: 'New Post' }, 201)
  )

const client = hc<typeof app>('http://localhost')

const res = await client.posts.$post({
  json: {
    title: 'Hello Hono',
    content: 'My first post'
  }
})

if (res.status === 201) {
  const post = await res.json()
  console.log('Created post:', post.id)
}
```

### POST with Form Data

Send form data using the `form` property:

```typescript theme={null}
const app = new Hono()
  .post('/upload',
    validator('form', () => ({ title: '', file: new File([], '') })),
    (c) => c.json({ success: true })
  )

const client = hc<typeof app>('http://localhost')

const res = await client.upload.$post({
  form: {
    title: 'My File',
    file: selectedFile // File object from input
  }
})
```

### Multiple Form Values

Form fields can have multiple values:

```typescript theme={null}
await client['form-endpoint'].$post({
  form: {
    tags: ['tag1', 'tag2', 'tag3'],
    title: 'Single value'
  }
})
```

## Path Parameters

Access routes with path parameters using bracket notation:

```typescript theme={null}
const app = new Hono()
  .get('/posts/:id', (c) => c.json({ id: 123, title: 'Post' }))
  .put('/posts/:id', (c) => c.json({ success: true }))
  .delete('/posts/:id', (c) => c.json({ deleted: true }))

const client = hc<typeof app>('http://localhost')

// GET /posts/123
await client.posts[':id'].$get({
  param: { id: '123' }
})

// PUT /posts/123
await client.posts[':id'].$put({
  param: { id: '123' },
  json: { title: 'Updated' }
})

// DELETE /posts/123
await client.posts[':id'].$delete({
  param: { id: '123' }
})
```

### Multiple Path Parameters

```typescript theme={null}
const app = new Hono()
  .get('/posts/:postId/comments/:commentId', (c) => {
    return c.json({ postId: 1, commentId: 2, text: 'Great!' })
  })

const client = hc<typeof app>('http://localhost')

const res = await client.posts[':postId'].comments[':commentId'].$get({
  param: {
    postId: '123',
    commentId: '456'
  }
})
```

## Headers and Cookies

### Request Headers

Add custom headers to individual requests:

```typescript theme={null}
const res = await client.api.protected.$get({
  header: {
    'Authorization': 'Bearer token123',
    'X-Custom-Header': 'value'
  }
})
```

### Global Headers

Set headers for all requests when creating the client:

```typescript theme={null}
const client = hc<typeof app>('http://localhost', {
  headers: {
    'Authorization': 'Bearer token123',
    'X-App-Version': '1.0.0'
  }
})
```

### Dynamic Headers

Use a function to compute headers dynamically:

```typescript theme={null}
let token = 'initial-token'

const client = hc<typeof app>('http://localhost', {
  headers: () => ({
    'Authorization': `Bearer ${token}`,
    'X-Timestamp': Date.now().toString()
  })
})

// Headers are evaluated on each request
await client.api.$get() // Uses current token value

token = 'new-token'
await client.api.$get() // Uses updated token
```

### Async Headers

Headers can be computed asynchronously:

```typescript theme={null}
const client = hc<typeof app>('http://localhost', {
  headers: async () => {
    const token = await getTokenFromStorage()
    return {
      'Authorization': `Bearer ${token}`
    }
  }
})
```

### Cookies

Send cookies with your requests:

```typescript theme={null}
const res = await client.api.$get({
  cookie: {
    sessionId: 'abc123',
    theme: 'dark'
  }
})
```

## Handling Responses

### Response Object

The client returns a standard `Response` object with typed methods:

```typescript theme={null}
const res = await client.posts.$get()

// Check status
if (res.ok) {
  console.log('Success!')
}
console.log(res.status) // 200, 404, etc.

// Access headers
const contentType = res.headers.get('content-type')

// Parse body (type-safe)
const data = await res.json() // TypeScript knows the shape
```

### JSON Responses

```typescript theme={null}
const app = new Hono()
  .get('/user', (c) => c.json({ id: 1, name: 'Alice' }))

const client = hc<typeof app>('http://localhost')
const res = await client.user.$get()
const user = await res.json()

// TypeScript knows:
// user.id is number
// user.name is string
console.log(user.name)
```

### Text Responses

```typescript theme={null}
const app = new Hono()
  .get('/message', (c) => c.text('Hello, Hono!'))

const client = hc<typeof app>('http://localhost')
const res = await client.message.$get()
const text = await res.text()

// text is typed as 'Hello, Hono!' (literal type)
console.log(text)
```

### Blob and Binary Data

```typescript theme={null}
const res = await client.download.$get()
const blob = await res.blob()
const arrayBuffer = await res.arrayBuffer()
const bytes = await res.bytes()
```

## Error Handling

### Status Code Checking

Handle different status codes with type narrowing:

```typescript theme={null}
const res = await client.posts[':id'].$get({ param: { id: '123' } })

if (res.status === 200) {
  const post = await res.json()
  // TypeScript knows this is the success type
  console.log(post.title)
} else if (res.status === 404) {
  const error = await res.json()
  // TypeScript knows this is the error type
  console.error(error.message)
}
```

### Using `res.ok`

Check if the response is successful (status 200-299):

```typescript theme={null}
const res = await client.posts.$post({ json: { title: 'New' } })

if (res.ok) {
  // Status is 2xx
  const data = await res.json()
  console.log('Success:', data)
} else {
  // Status is not 2xx
  console.error('Failed with status:', res.status)
}
```

<Note>
  When using `res.ok`, TypeScript narrows the type to only success status codes.
</Note>

### Parsing Response Errors

Use the `parseResponse` utility for automatic error handling:

```typescript theme={null}
import { parseResponse } from 'hono/client'

try {
  const data = await parseResponse(client.posts.$get())
  // data is automatically parsed and typed
  console.log(data)
} catch (error) {
  // Throws DetailedError for non-2xx responses
  if (error instanceof DetailedError) {
    console.error('Status:', error.response.status)
    console.error('Body:', error.body)
  }
}
```

### Middleware Error Responses

Handle errors returned from middleware:

```typescript theme={null}
const app = new Hono()
  .post('/posts',
    async (c, next) => {
      const auth = c.req.header('authorization')
      if (!auth) {
        return c.json({ error: 'Unauthorized' }, 401)
      }
      return next()
    },
    validator('json', (input, c) => {
      if (!input.title) {
        return c.json({ error: 'Bad request' }, 400)
      }
      return input
    }),
    (c) => c.json({ id: 1 }, 200)
  )

const client = hc<typeof app>('http://localhost')
const res = await client.posts.$post({ json: { title: 'Post' } })

// TypeScript knows about all possible status codes
if (res.status === 200) {
  const data = await res.json() // { id: number }
} else if (res.status === 400) {
  const error = await res.json() // { error: 'Bad request' }
} else if (res.status === 401) {
  const error = await res.json() // { error: 'Unauthorized' }
}
```

## Advanced Features

### Custom Fetch Implementation

Provide your own fetch implementation:

```typescript theme={null}
import { hc } from 'hono/client'
import nodeFetch from 'node-fetch'

const client = hc<typeof app>('http://localhost', {
  fetch: nodeFetch as typeof fetch
})
```

### Using with `app.request`

Test your API without making network calls:

```typescript theme={null}
import { Hono } from 'hono'
import { hc } from 'hono/client'

const app = new Hono()
  .get('/hello', (c) => c.json({ message: 'Hello!' }))

const client = hc<typeof app>('', { fetch: app.request })

// Makes request directly to the app (no HTTP)
const res = await client.hello.$get()
const data = await res.json()
```

<Info>
  This is perfect for testing - no need to start a server!
</Info>

### Custom RequestInit

Pass custom `RequestInit` options:

```typescript theme={null}
const client = hc<typeof app>('http://localhost', {
  init: {
    credentials: 'include',
    mode: 'cors',
    cache: 'no-cache'
  }
})
```

You can also override per request:

```typescript theme={null}
await client.api.$get(undefined, {
  init: {
    signal: abortController.signal,
    cache: 'force-cache'
  }
})
```

<Warning>
  The `init` option takes the highest priority and can overwrite Hono's settings for `body`, `method`, and `headers`.
</Warning>

### Custom Query Serialization

Customize how query parameters are serialized:

```typescript theme={null}
const client = hc<typeof app>('http://localhost', {
  buildSearchParams: (query) => {
    const params = new URLSearchParams()
    for (const [key, value] of Object.entries(query)) {
      if (Array.isArray(value)) {
        // Use bracket notation for arrays
        value.forEach(item => params.append(`${key}[]`, item))
      } else {
        params.set(key, value)
      }
    }
    return params
  }
})

// query: { tags: ['a', 'b'] }
// becomes: ?tags[]=a&tags[]=b
// instead of: ?tags=a&tags=b
```

### WebSocket Support

Connect to WebSocket endpoints:

```typescript theme={null}
import { upgradeWebSocket } from 'hono/adapter'

const app = new Hono()
  .get('/ws', upgradeWebSocket((c) => ({
    onMessage(event, ws) {
      ws.send('Hello from server!')
    }
  })))

const client = hc<typeof app>('http://localhost')
const ws = client.ws.$ws()

ws.addEventListener('message', (event) => {
  console.log('Received:', event.data)
})
```

## URL Utilities

### Get Full URL

Generate the full URL for a route:

```typescript theme={null}
const url = client.api.posts[':id'].$url({
  param: { id: '123' },
  query: { format: 'json' }
})

console.log(url.href) // http://localhost/api/posts/123?format=json
console.log(url.pathname) // /api/posts/123
console.log(url.search) // ?format=json
```

### Get Path Only

Generate just the path (without domain):

```typescript theme={null}
const path = client.api.posts[':id'].$path({
  param: { id: '123' },
  query: { format: 'json' }
})

console.log(path) // /api/posts/123?format=json
```

<Info>
  These utilities are useful for generating links in your UI or for debugging.
</Info>

## Best Practices

<AccordionGroup>
  <Accordion title="Create a shared client instance">
    Export a configured client to use throughout your app:

    ```typescript theme={null}
    // client.ts
    import { hc } from 'hono/client'
    import type { AppType } from './server'

    export const client = hc<AppType>(import.meta.env.VITE_API_URL)
    ```
  </Accordion>

  <Accordion title="Handle errors consistently">
    Create a wrapper for consistent error handling:

    ```typescript theme={null}
    async function apiCall<T>(promise: Promise<T>) {
      const res = await promise
      if (!res.ok) {
        throw new Error(`API Error: ${res.status}`)
      }
      return res
    }
    ```
  </Accordion>

  <Accordion title="Use environment variables for URLs">
    Don't hardcode API URLs:

    ```typescript theme={null}
    const client = hc<typeof app>(process.env.API_URL || 'http://localhost:8787')
    ```
  </Accordion>

  <Accordion title="Leverage TypeScript's type narrowing">
    Use status code checks to narrow response types:

    ```typescript theme={null}
    if (res.status === 200) {
      // TypeScript knows the exact response type here
    }
    ```
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Validators" icon="check" href="/guides/validation">
    Add runtime validation to your API
  </Card>

  <Card title="Middleware" icon="layer-group" href="/middleware/overview">
    Learn about Hono's middleware system
  </Card>
</CardGroup>
