Last Updated: 3/11/2026
RPC
Hono’s RPC feature enables type-safe communication between server and client. Share API specifications through TypeScript types without code generation.
Server Setup
Define your routes with validators:
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
title: z.string(),
body: z.string(),
})
),
(c) => {
return c.json(
{
ok: true,
message: 'Created!',
},
201
)
}
)
// Export the route type
export type AppType = typeof routeClient Setup
Import the type and create a client:
import { hc } from 'hono/client'
import type { AppType } from './server'
const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
if (res.ok) {
const data = await res.json()
console.log(data.message) // "Created!"
}Path Parameters
Handle dynamic routes:
// Server
const route = app.get(
'/posts/:id',
zValidator(
'query',
z.object({
page: z.coerce.number().optional(),
})
),
(c) => {
const id = c.req.param('id')
return c.json({ id, title: 'Post', body: 'Content' })
}
)
// Client
const res = await client.posts[':id'].$get({
param: { id: '123' },
query: { page: '1' }, // Must be string, converted by validator
})Multiple Parameters
// Server
app.get('/posts/:postId/:authorId', (c) => {
// ...
})
// Client
const res = await client.posts[':postId'][':authorId'].$get({
param: {
postId: '123',
authorId: '456',
},
})Status Codes
Explicit status codes are typed:
// Server
const app = new Hono().get(
'/posts',
zValidator('query', z.object({ id: z.string() })),
async (c) => {
const { id } = c.req.valid('query')
const post = await getPost(id)
if (!post) {
return c.json({ error: 'not found' }, 404)
}
return c.json({ post }, 200)
}
)
// Client
const res = await client.posts.$get({ query: { id: '123' } })
if (res.status === 404) {
const data = await res.json() // { error: string }
console.log(data.error)
}
if (res.ok) {
const data = await res.json() // { post: Post }
console.log(data.post)
}Type Inference
Infer request and response types:
import type { InferRequestType, InferResponseType } from 'hono/client'
const $post = client.posts.$post
// Request type
type ReqType = InferRequestType<typeof $post>['form']
// Response type
type ResType = InferResponseType<typeof $post>
// Response type for specific status
type ResType200 = InferResponseType<typeof $post, 200>Headers
Add custom headers:
const res = await client.api.posts.$get(
{ query: { id: '123' } },
{
headers: {
'X-Custom-Header': 'value',
Authorization: 'Bearer token',
},
}
)Add common headers for all requests:
const client = hc<AppType>('/api', {
headers: {
Authorization: 'Bearer TOKEN',
},
})Cookies
Send cookies with requests:
const client = hc<AppType>('http://localhost:8787/', {
init: {
credentials: 'include',
},
})
const res = await client.posts.$get({ query: { id: '123' } })File Uploads
Upload files:
// Client
const res = await client.user.picture.$put({
form: {
file: new File([fileToUpload], filename, {
type: fileToUpload.type,
}),
},
})
// Server
app.put(
'/user/picture',
zValidator('form', z.object({ file: z.instanceof(File) }))
// ...
)$url() and $path()
Get URL or path:
const client = hc<typeof route>('http://localhost:8787/')
// Get URL object
const url = client.api.posts.$url()
console.log(url.pathname) // '/api/posts'
// Get path string
const path = client.api.posts.$path()
console.log(path) // '/api/posts'
// With parameters
const path = client.api.posts[':id'].$path({
param: { id: '123' },
query: { page: '1' },
})
console.log(path) // '/api/posts/123?page=1'Larger Applications
For larger apps, chain route handlers:
// authors.ts
const authors = new Hono()
.get('/', (c) => c.json('list authors'))
.post('/', (c) => c.json('create author', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default authors
// books.ts
const books = new Hono()
.get('/', (c) => c.json('list books'))
.post('/', (c) => c.json('create book', 201))
.get('/:id', (c) => c.json(`get ${c.req.param('id')}`))
export default books
// index.ts
import authors from './authors'
import books from './books'
const app = new Hono()
const routes = app
.route('/authors', authors)
.route('/books', books)
export default app
export type AppType = typeof routesUsing with SWR
Integrate with React hooks:
import useSWR from 'swr'
import { hc } from 'hono/client'
import type { InferRequestType } from 'hono/client'
import type { AppType } from '../server'
const App = () => {
const client = hc<AppType>('/api')
const $get = client.hello.$get
const fetcher = (arg: InferRequestType<typeof $get>) => async () => {
const res = await $get(arg)
return await res.json()
}
const { data, error, isLoading } = useSWR(
'api-hello',
fetcher({ query: { name: 'SWR' } })
)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <h1>{data?.message}</h1>
}Performance Tips
Compile Before Using
For better IDE performance, compile types:
import { app } from './app'
import { hc } from 'hono/client'
export type Client = ReturnType<typeof hc<typeof app>>
export const hcWithType = (...args: Parameters<typeof hc>): Client =>
hc<typeof app>(...args)After compiling, use hcWithType instead of hc.
Next Steps
Learn about Testing your Hono applications.