株式会社オブライト
Software Dev2026-04-08

Hono RPC Mode Complete Guide — Type-Safe Fullstack Development Without Code Generation [2026]

Hono's RPC mode delivers tRPC-like type-safe fullstack development without code generation or OpenAPI. Complete 2026 guide covering hc client, InferRequestType, multi-route integration, and production patterns.


What Is Hono RPC Mode?

Hono RPC mode lets the client automatically infer server-side route types — no code generation or OpenAPI spec required. One line, `export type AppType = typeof route`, connects server and client through TypeScript types, delivering a tRPC-like developer experience natively within Hono.

How Hono RPC Works

Loading diagram...

The server exports `AppType`, which is passed directly to `hc<AppType>`. TypeScript validates everything at build time, eliminating an entire class of runtime errors before they reach production.

Hono RPC vs. tRPC vs. OpenAPI vs. GraphQL

The table below compares the major type-safe API approaches.

CriteriaHono RPCtRPCOpenAPIGraphQL
Code generationNot requiredNot requiredRequiredRequired
Schema definitionZod (optional)Zod/YupYAML/JSONSDL
Framework lock-inHono onlyFramework-agnosticAgnosticAgnostic
Bundle sizeMinimalMediumLargeLarge
Edge runtimeNativeLimitedAgnosticAgnostic
Learning curveLowLow–MediumMediumHigh

Server-Side Implementation

Define your routes with Hono and export the type. The `zValidator` middleware validates request bodies and feeds the type information into the RPC chain.

ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()
const route = app.post(
  '/posts',
  zValidator('json', z.object({
    title: z.string().min(1),
    body: z.string()
  })),
  (c) => {
    const data = c.req.valid('json')
    return c.json({ ok: true, id: 1, ...data }, 201)
  }
)
export type AppType = typeof route
export default app

Client-Side Implementation

Pass `AppType` to the `hc` factory function. Method names, argument shapes, and return types are all fully inferred — your IDE's autocomplete will guide you through every call.

ts
import type { AppType } from './server'
import { hc } from 'hono/client'

const client = hc<AppType>('http://localhost:8787/')
const res = await client.posts.$post({
  json: { title: 'Hello', body: 'Hono RPC is great!' }
})
if (res.ok) {
  const data = await res.json()
  // data: { ok: true, id: number, title: string, body: string }
}

InferRequestType and InferResponseType

Use these type helpers to extract the exact request and response shapes from your client. They are invaluable for form validation, test fixtures, and shared type libraries.

ts
import { InferRequestType, InferResponseType } from 'hono/client'
import type { AppType } from './server'
import { hc } from 'hono/client'

const client = hc<AppType>('/')
type CreatePostInput = InferRequestType<typeof client.posts.$post>['json']
// => { title: string; body: string }

type CreatePostOutput = InferResponseType<typeof client.posts.$post, 201>
// => { ok: boolean; id: number; title: string; body: string }

$url() and $path() Utilities

These utilities generate URL objects and path strings from the client, eliminating hardcoded URL strings across your codebase. Renaming a route no longer requires a find-and-replace across every file.

ts
const url = client.posts.$url()
// => URL { href: 'http://localhost:8787/posts' }

const path = client.posts.$path()
// => '/posts'

// With path parameters
const postUrl = client.posts[':id'].$url({ param: { id: '42' } })
// => URL { href: 'http://localhost:8787/posts/42' }

Multi-Route Integration — Modular RPC API Design

For larger applications, split routes by domain and merge them into a single `app`. The exported `AppType` encompasses all routes, so the `hc` client gets the full type picture.

ts
// routes/posts.ts
const postsRoute = new Hono()
  .get('/', (c) => c.json({ posts: [] }))
  .post('/', zValidator('json', postSchema), (c) => {
    return c.json({ ok: true }, 201)
  })
export type PostsType = typeof postsRoute

// routes/users.ts
const usersRoute = new Hono()
  .get('/:id', (c) => c.json({ user: { id: c.req.param('id') } }))
export type UsersType = typeof usersRoute

// app.ts
const app = new Hono()
  .route('/posts', postsRoute)
  .route('/users', usersRoute)
export type AppType = typeof app
export default app

Integration with Next.js and React

Keep the `hc` client as a singleton to stabilize type inference and avoid repeated instantiation costs. The pattern below works with both Server Components and Client Components in the Next.js App Router.

ts
// lib/client.ts
import { hc } from 'hono/client'
import type { AppType } from '@/server/app'

export const client = hc<AppType>(process.env.NEXT_PUBLIC_API_URL!)

// app/posts/page.tsx (Server Component)
import { client } from '@/lib/client'

export default async function PostsPage() {
  const res = await client.posts.$get()
  const { posts } = await res.json()
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

Error Handling

Hono's `HTTPException` provides a consistent error contract across the entire API surface. Errors thrown inside route handlers are caught by the global error handler and serialized cleanly.

ts
import { HTTPException } from 'hono/http-exception'

const route = app
  .get('/posts/:id', async (c) => {
    const post = await db.find(c.req.param('id'))
    if (!post) throw new HTTPException(404, { message: 'Not Found' })
    return c.json({ post }, 200)
  })

app.onError((err, c) => {
  if (err instanceof HTTPException) return err.getResponse()
  return c.json({ error: 'Internal Server Error' }, 500)
})

Type Inference Constraints and Optimization

At scale, TypeScript compile times can increase. The following strategies keep builds fast.

StrategyEffect
Split routes by domainNarrows the type inference scope
Avoid deeply nested `satisfies`Reduces type complexity
Singleton `hc` clientEliminates repeated instantiation overhead
TypeScript 5.5+ incremental buildsDifferential compilation for faster rebuilds

Migrating from Existing OpenAPI Projects

A gradual migration is the safest path. Use `@hono/zod-openapi` and `@hono/swagger-ui` to maintain an OpenAPI document while introducing Hono RPC for new endpoints. Migrate legacy endpoints incrementally, validating type coverage at each step.

FAQ

Q1. Can Hono RPC fully replace tRPC? A. It depends on your stack. tRPC has a mature Next.js integration and is framework-agnostic. Hono RPC shines when you are already running a Hono server or targeting Edge runtimes — no extra library required. Q2. Does the hc client work in the browser? A. Yes. `hono/client` is built on Web Standards and runs in Node.js, Bun, Deno, and modern browsers without modification. Q3. Can file uploads be handled type-safely? A. Use `zValidator('form', ...)` on the server to validate FormData. The client will infer the correct form shape from that definition. Q4. Does Hono RPC support WebSockets? A. Not currently. Hono RPC targets HTTP request/response pairs. Use Hono's native WebSocket support (`hono/ws`) for real-time communication. Q5. Is Zod mandatory for RPC mode? A. No. `zValidator` is optional. Routes without validators still export a valid `AppType`, though request type precision will be reduced. Q6. Does it work on Cloudflare Workers? A. Fully supported. Hono was built with Cloudflare Workers as a first-class target, and RPC mode works identically at the Edge. Q7. How do I handle multiple microservices with different base URLs? A. Create a separate `hc<ServiceType>(baseUrl)` client per service. Each client has its own isolated type context, preserving full type safety across service boundaries.

How Oflight Can Help

Oflight's engineers specialize in type-safe fullstack architecture using Hono, TypeScript, and modern Edge runtimes. Whether you're starting a greenfield project or migrating a legacy REST API, we'll guide you from design to deployment. Initial consultations are free. Learn more at our Software Development service page.

Feel free to contact us

Contact Us