Skip to Content
guidesRpc

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 route

Client 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 routes

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