> ## 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.

# hc()

> Create a type-safe RPC client for your Hono application

## Overview

The `hc()` function creates a type-safe HTTP client that automatically infers types from your Hono server routes. It uses Proxy objects to provide a chainable API that mirrors your server's route structure.

## Import

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

## Function Signature

```typescript theme={null}
function hc<T extends Hono<any, any, any>, Prefix extends string = string>(
  baseUrl: Prefix,
  options?: ClientRequestOptions
): Client<T, Prefix>
```

<ParamField path="baseUrl" type="string" required>
  The base URL of your API server (e.g., `'http://localhost:3000'` or `'https://api.example.com'`)
</ParamField>

<ParamField path="options" type="ClientRequestOptions">
  Configuration options for the client

  <Expandable title="properties">
    <ParamField path="fetch" type="typeof fetch">
      Custom fetch implementation. Defaults to global `fetch`
    </ParamField>

    <ParamField path="headers" type="Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>)">
      Headers to include with every request. Can be an object or a function that returns headers (useful for dynamic auth tokens)
    </ParamField>

    <ParamField path="init" type="RequestInit">
      Standard `RequestInit` options. **Caution**: This takes highest priority and can overwrite settings that Hono sets automatically (like `body`, `method`, `headers`)
    </ParamField>

    <ParamField path="buildSearchParams" type="BuildSearchParamsFn">
      Custom function to serialize query parameters into URLSearchParams. By default, arrays are serialized as multiple parameters with the same key (e.g., `key=a&key=b`). You can provide a custom function to change this behavior.

      ```typescript theme={null}
      type BuildSearchParamsFn = (query: Record<string, string | string[]>) => URLSearchParams
      ```

      **Example**: Using bracket notation for arrays

      ```typescript theme={null}
      import qs from 'qs'

      const client = hc('http://localhost:3000', {
        buildSearchParams: (query) => {
          return new URLSearchParams(qs.stringify(query))
        }
      })
      ```
    </ParamField>

    <ParamField path="webSocket" type="(...args: ConstructorParameters<typeof WebSocket>) => WebSocket">
      Custom WebSocket constructor for WebSocket connections
    </ParamField>
  </Expandable>
</ParamField>

<ResponseField name="Client" type="Client<T, Prefix>">
  A proxy-based client that mirrors your server's route structure with type-safe methods
</ResponseField>

## Usage

### Basic Usage

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

const client = hc<AppType>('http://localhost:3000')

// Make a GET request
const response = await client.api.posts.$get()
const data = await response.json()
```

### With Headers

```typescript theme={null}
// Static headers
const client = hc<AppType>('http://localhost:3000', {
  headers: {
    'Authorization': 'Bearer token123'
  }
})

// Dynamic headers (useful for auth tokens)
const client = hc<AppType>('http://localhost:3000', {
  headers: async () => {
    const token = await getAuthToken()
    return {
      'Authorization': `Bearer ${token}`
    }
  }
})
```

### Custom Fetch Implementation

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

const client = hc<AppType>('http://localhost:3000', {
  fetch: customFetch // Useful for testing with MSW or node-fetch
})
```

## Request Methods

The client provides methods for all HTTP verbs, prefixed with `$`:

* `$get(args?, options?)` - GET request
* `$post(args?, options?)` - POST request
* `$put(args?, options?)` - PUT request
* `$patch(args?, options?)` - PATCH request
* `$delete(args?, options?)` - DELETE request
* `$options(args?, options?)` - OPTIONS request
* `$head(args?, options?)` - HEAD request

### Request Arguments

Each method accepts an optional `args` object with the following properties:

<ParamField path="query" type="Record<string, string | string[]>">
  Query parameters for the request
</ParamField>

<ParamField path="param" type="Record<string, string>">
  Path parameters (e.g., for routes like `/posts/:id`)
</ParamField>

<ParamField path="json" type="object">
  JSON body data (automatically sets `Content-Type: application/json`)
</ParamField>

<ParamField path="form" type="Record<string, string | Blob | File | (string | Blob | File)[]>">
  Form data (automatically creates `FormData` and handles arrays)
</ParamField>

<ParamField path="header" type="Record<string, string>">
  Additional headers for this specific request
</ParamField>

<ParamField path="cookie" type="Record<string, string>">
  Cookies to send with the request
</ParamField>

### Example Requests

```typescript theme={null}
// GET with query parameters
const res = await client.api.search.$get({
  query: {
    q: 'hono',
    limit: '10'
  }
})

// POST with JSON body
const res = await client.api.posts.$post({
  json: {
    title: 'Hello Hono',
    content: 'This is a post'
  }
})

// PUT with path parameters
const res = await client.api.posts[':id'].$put({
  param: { id: '123' },
  json: { title: 'Updated Title' }
})

// DELETE with path parameters
const res = await client.api.posts[':id'].$delete({
  param: { id: '123' }
})

// POST with form data
const res = await client.api.upload.$post({
  form: {
    file: fileBlob,
    name: 'document.pdf'
  }
})
```

## Utility Methods

### \$url()

Generates a fully-qualified URL object for the route.

```typescript theme={null}
const url = client.api.posts[':id'].$url({
  param: { id: '123' },
  query: { details: 'true' }
})
// Returns: URL object for "http://localhost:3000/api/posts/123?details=true"
```

**Type Signature**:

```typescript theme={null}
$url(arg?: { param?: Record<string, string>, query?: Record<string, string> }): URL
```

### \$path()

Generates the path portion of the URL (without the base URL).

```typescript theme={null}
const path = client.api.posts[':id'].$path({
  param: { id: '123' },
  query: { details: 'true' }
})
// Returns: "/api/posts/123?details=true"
```

**Type Signature**:

```typescript theme={null}
$path(arg?: { param?: Record<string, string>, query?: Record<string, string> }): string
```

### \$ws()

Creates a WebSocket connection for routes that support WebSocket upgrades.

```typescript theme={null}
const ws = client.api.ws.$ws({
  query: { token: 'auth-token' }
})

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

**Type Signature**:

```typescript theme={null}
$ws(args?: { param?: Record<string, string>, query?: Record<string, string> }): WebSocket
```

## Response Handling

All HTTP methods return a `Promise<ClientResponse>` which extends the standard `Response` interface with type-safe methods:

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

// Type-safe JSON parsing
const data = await response.json() // Type is inferred from server

// Text response
const text = await response.text()

// Other Response methods
const blob = await response.blob()
const buffer = await response.arrayBuffer()
const bytes = await response.bytes()
const formData = await response.formData()

// Response properties
console.log(response.status)      // HTTP status code
console.log(response.ok)          // true if status is 2xx
console.log(response.headers)     // Response headers
console.log(response.statusText)  // Status text
```

## Per-Request Options

You can override client-level options for individual requests:

```typescript theme={null}
const client = hc<AppType>('http://localhost:3000')

// Override headers for this request only
const response = await client.api.posts.$get(
  { query: { limit: '10' } },
  {
    headers: {
      'X-Custom-Header': 'value'
    }
  }
)

// Use custom fetch for this request
const response = await client.api.posts.$get(
  undefined,
  {
    fetch: mockFetch,
    init: {
      signal: abortController.signal
    }
  }
)
```

## Type Safety

The client automatically infers request and response types from your server definition:

```typescript theme={null}
// On the server
const app = new Hono()
  .get('/posts/:id', (c) => {
    return c.json({
      id: c.req.param('id'),
      title: 'Hello',
      author: { name: 'John' }
    })
  })

export type AppType = typeof app

// On the client
import type { AppType } from './server'

const client = hc<AppType>('http://localhost:3000')

// TypeScript knows the shape of the response
const res = await client.posts[':id'].$get({ param: { id: '123' } })
const data = await res.json()
// data.title        ✓ string
// data.author.name  ✓ string
// data.invalid      ✗ TypeScript error
```

See [api/client/types](/api/client/types) for helper types like `InferRequestType` and `InferResponseType`.

## Notes

* The client automatically handles URL parameter replacement for path parameters like `:id`
* Index routes (ending in `/index`) are automatically normalized
* GET and HEAD requests never include a body, even if you accidentally provide one
* FormData is automatically created when using the `form` option
* Array values in forms are automatically expanded (multiple form fields with the same key)
* The client is built on Proxy objects, so it works with any route structure without requiring code generation
