RPC / Client
The RPC feature allows sharing the API specifications between the server and the client.
You can export the types of input type specified by the Validator and the output type emitted by json()
. And Hono Client will able to import it.
Server
All you need to do on the server side is to write a validator, create a variable route
.
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
title: z.string(),
body: z.string(),
})
),
(c) => {
// ...
return c.json(
{
ok: true,
message: 'Created!',
},
201
)
}
)
const route = app.post(
'/posts',
zValidator(
'form',
z.object({
title: z.string(),
body: z.string(),
})
),
(c) => {
// ...
return c.json(
{
ok: true,
message: 'Created!',
},
201
)
}
)
Then, export the type to share the API spec with the Client.
export type AppType = typeof route
export type AppType = typeof route
Client
On the Client side, import hc
and AppType
first.
import { AppType } from '.'
import { hc } from 'hono/client'
import { AppType } from '.'
import { hc } from 'hono/client'
hc
is a function to create a client. Pass AppType
as Generics and specify the server URL as an argument.
const client = hc<AppType>('http://localhost:8787/')
const client = hc<AppType>('http://localhost:8787/')
Call client.{path}.{method}
and pass the data you wish to send to the server as an argument.
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
const res = await client.posts.$post({
form: {
title: 'Hello',
body: 'Hono is a cool project',
},
})
The res
is compatible with "fetch" Response. You can retrieve data from the server with res.json()
.
if (res.ok) {
const data = await res.json()
console.log(data.message)
}
if (res.ok) {
const data = await res.json()
console.log(data.message)
}
Path parameters
You can also handle routes that include path parameters.
const route = app.get(
'/posts/:id',
zValidator(
'query',
z.object({
page: z.string().optional(),
})
),
(c) => {
// ...
return c.json({
title: 'Night',
body: 'Time to sleep',
})
}
)
const route = app.get(
'/posts/:id',
zValidator(
'query',
z.object({
page: z.string().optional(),
})
),
(c) => {
// ...
return c.json({
title: 'Night',
body: 'Time to sleep',
})
}
)
Specify the string you want to include in the path with param
.
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
query: {},
})
const res = await client.posts[':id'].$get({
param: {
id: '123',
},
query: {},
})
Headers
You can append the headers to the request.
const res = await client.search.$get(
{
//...
},
{
headers: {
'X-Custom-Header': 'Here is Hono Client',
'X-User-Agent': 'hc',
},
}
)
const res = await client.search.$get(
{
//...
},
{
headers: {
'X-Custom-Header': 'Here is Hono Client',
'X-User-Agent': 'hc',
},
}
)
To add a common header to all requests, specify it as an argument to the hc
function.
const client = hc<AppType>('/api', {
headers: {
Authorization: 'Bearer TOKEN',
},
})
const client = hc<AppType>('/api', {
headers: {
Authorization: 'Bearer TOKEN',
},
})
$url()
You can get a URL
object for accessing the endpoint by using $url()
.
WARNING
You have to pass in an absolute URL for this to work. Passing in a relative URL /
will result in the following error.
Uncaught TypeError: Failed to construct 'URL': Invalid URL
// ❌ Will throw error
const client = hc<typeof route>('/')
client.api.post.$url()
// ✅ Will work as expected
const client = hc<AppType>('http://localhost:8787')
client.api.post.$url()
// ❌ Will throw error
const client = hc<typeof route>('/')
client.api.post.$url()
// ✅ Will work as expected
const client = hc<AppType>('http://localhost:8787')
client.api.post.$url()
const route = app
.get('/api/posts', (c) => c.json({ posts }))
.get('/api/posts/:id', (c) => c.json({ post }))
const client = hc<typeof route>('http://localhost:8787/')
let url = client.api.posts.$url()
console.log(url.pathname) // `/api/posts`
url = client.api.posts[':id'].$url({
param: {
id: '123',
},
})
console.log(url.pathname) // `/api/posts/123`
const route = app
.get('/api/posts', (c) => c.json({ posts }))
.get('/api/posts/:id', (c) => c.json({ post }))
const client = hc<typeof route>('http://localhost:8787/')
let url = client.api.posts.$url()
console.log(url.pathname) // `/api/posts`
url = client.api.posts[':id'].$url({
param: {
id: '123',
},
})
console.log(url.pathname) // `/api/posts/123`
Custom fetch
method
You can set the custom fetch
method.
In the following example script for Cloudflare Worker, Service Bindings' fetch
method is used instead of the default fetch
.
# wrangler.toml
services = [
{ binding = "AUTH", service = "auth-service" },
]
# wrangler.toml
services = [
{ binding = "AUTH", service = "auth-service" },
]
// src/client.ts
const client = hc<CreateProfileType>('/', { fetch: c.env.AUTH.fetch.bind(c.env.AUTH) })
// src/client.ts
const client = hc<CreateProfileType>('/', { fetch: c.env.AUTH.fetch.bind(c.env.AUTH) })
Infer
Use InferRequestType
and InferResponseType
to know the type of object to be requested and the type of object to be returned.
import type { InferRequestType, InferResponseType } from 'hono/client'
// InferRequestType
const $post = client.todo.$post
type ReqType = InferRequestType<typeof $post>['form']
// InferResponseType
type ResType = InferResponseType<typeof $post>
import type { InferRequestType, InferResponseType } from 'hono/client'
// InferRequestType
const $post = client.todo.$post
type ReqType = InferRequestType<typeof $post>['form']
// InferResponseType
type ResType = InferResponseType<typeof $post>
Using SWR
You can also use a React Hook library such as SWR.
import useSWR from 'swr'
import { hc } from 'hono/client'
import type { InferRequestType } from 'hono/client'
import { AppType } from '../functions/api/[[route]]'
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>
}
export default App
import useSWR from 'swr'
import { hc } from 'hono/client'
import type { InferRequestType } from 'hono/client'
import { AppType } from '../functions/api/[[route]]'
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>
}
export default App