Whenever taking on smaller backend APIs in custom development, there is a recurring feeling of restarting the exact same work from zero: writing Express app initialization, lining up CORS and logger middleware, manually adding request body validation, and finally configuring the deployment setup. Although the internal business logic differs by project, the foundational code reinforcing it becomes a minor tweak on copy-paste almost every time. Types are even more troublesome. The shape of server responses must be manually defined all over again on the frontend. Every time an API is modified, duplicating type changes across both server and frontend had become standard practice.
The root cause of this fatigue lies in framework selection stagnating at "Express for now." Express is mature with abundant documentation, but it does not offer seamless answers to three key demands in modern custom development: TypeScript compatibility, edge/serverless deployment, and frontend type sharing. Consequently, adopting Hono as the foundation for small-to-medium client APIs has surged in recent years. In this article, we break down the practical considerations for building backends with Hono—covering directory structures, validation, type sharing, and deployment selection—alongside real project decisions.
Why Hono for Client APIs Today
Hono is a lightweight web framework built on standard Web APIs (Fetch API). Its defining feature is that identical code runs across runtimes. Whether on Node.js, Bun, Deno, Cloudflare Workers, AWS Lambda, Vercel, or Netlify, it runs with virtually no rewrites in any environment supporting the Fetch API. Because infrastructure prerequisites shift depending on the client project, the ability to "swap deployment targets without rewriting code" proves exceptionally powerful.
The numbers support this. Weekly npm downloads exceeded 9 million as of January 2026, surging from around 600,000 a year earlier. Production adoption lists actual services such as Cloudflare (internal APIs for D1 and Workers KV), Clerk, Unkey, OpenStatus, and cdnjs. It has already moved past the "too new to trust" phase, becoming a premier option for edge and serverless foundations. As of writing (June 2026), the v4 release line is the stable branch.
From a custom development perspective, Hono excels in the following project profiles:
| Project Characteristics | Why Hono Fits |
|---|---|
| Small-to-medium REST APIs and BFFs | Thin foundation, accelerating setup to business logic |
| Edge/serverless prerequisite | Deploys to Workers or Lambda without rewrites |
| Frontend handled by the same team | RPC shares types between server and frontend, removing duplication |
Conversely, for projects that have already structured massive domain layers with heavyweight frameworks like NestJS, or legacy systems with deep dependencies on Node-only libraries, there is little reason to force a migration. It is best to treat Hono as a dedicated tool for domains where "thin and fast foundations" hit the mark.
Dividing Directory Structures by "Feature" Rather Than "Layer"
Because Hono is an unopinionated framework that does not enforce project architecture, you must decide how to structure directories yourself. In custom development where multiple engineers contribute and long-term maintenance is involved, failing to establish this early leads to architectural collapse later.
The trending article "Personal Best Practices for Building Backends with Hono" (Zenn, published June 2026) outlines organizing by feature rather than by layer. Slicing horizontally by technical layer, such as controllers/, services/, and repositories/, forces developers to jump between multiple directories for every single feature adjustment. Instead, grouping by feature like src/features/users/ and src/features/posts/—co-locating route definitions, validation schemas, and logic—keeps modifications self-contained in one place.
src/
features/
users/
route.ts # ルート定義(Honoインスタンス)
schema.ts # zod/valibotスキーマ
handler.ts # 業務ロジック
posts/
route.ts
schema.ts
handler.ts
middleware/ # 横断的ミドルウェア(認証・ロガー等)
lib/ # DB接続・外部クライアント
app.ts # 各featureのルートを束ねるルート集約
To bundle routes, create Hono instances per feature and merge them with route(). Crucially, method chaining must be maintained here to preserve type inference for the RPC feature described later.
// src/features/users/route.ts
import { Hono } from 'hono'
const users = new Hono()
.get('/', (c) => c.json({ users: [] }))
.get('/:id', (c) => c.json({ id: c.req.param('id') }))
export default users
// src/app.ts
import { Hono } from 'hono'
import users from './features/users/route'
import posts from './features/posts/route'
const app = new Hono()
.route('/users', users)
.route('/posts', posts)
export default app
export type AppType = typeof app
Exporting AppType at the end becomes critical later on. This serves as the starting point for type sharing with the frontend.
Writing Standard Schema-Compatible Validation and Integrating Validators
In client APIs, incidents frequently arise where input validation is lax. Hono handles verification via its built-in validator middleware. For schema libraries, you can choose from options supporting Standard Schema (the common specification across validation libraries), such as zod, valibot, TypeBox, or ArkType.
The standard choice is @hono/zod-validator. You pass a zod-defined schema to zValidator and designate the validation target (such as json, query, or param). Validated data can then be extracted with type safety via c.req.valid().
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
const users = new Hono().post(
'/',
zValidator('json', schema),
(c) => {
const data = c.req.valid('json') // ここで name/email は型付き
return c.json({ created: data }, 201)
}
)
When optimizing bundle size—especially for edge deployments—using valibot via @hono/standard-validator offers a lighter alternative to zod. Because the API authoring experience is largely identical, a straightforward rule of thumb is: "use zod for persistent Node instances, and valibot when trimming size on Workers."
Responses for failed validation should be standardized. Because zValidator accepts a hook as its third argument, error response formats can be unified across the application.
const users = new Hono().post(
'/',
zValidator('json', schema, (result, c) => {
if (!result.success) {
return c.json(
{ error: 'ValidationError', issues: result.error.issues },
400
)
}
}),
(c) => c.json({ ok: true })
)
Additionally, if you want to auto-generate OpenAPI documentation via a schema-driven approach, combining it with hono-openapi is an option. For the merits and trade-offs of distributing types from an OpenAPI origin, refer to our article on type sharing from OpenAPI to oRPC, which compares alternative approaches to help inform your architecture including documentation generation.
Eliminating Redundant Frontend Type Maintenance with RPC
Hono provides its own distinct answer to the problem introduced earlier of writing types twice across server and frontend: the RPC feature. By passing the previously exported server-side AppType as a generic type to the frontend client hc, endpoint paths, request types, and response types propagate to the frontend. No code generation (codegen) is needed.
// フロント側
import { hc } from 'hono/client'
import type { AppType } from '../../server/src/app'
const client = hc<AppType>('https://api.example.com')
// パス補完が効き、resの型もサーバ定義から推論される
const res = await client.users.$post({
json: { name: 'Taro', email: 'taro@example.com' },
})
const data = await res.json()
Modifying a schema on the server immediately triggers frontend type errors (red squigglies) on affected lines. The dreaded issue where an API change breaks production because someone forgot to update the frontend is eliminated at build time. This delivers immense value in custom development.
However, there are two caveats to ensure type inference works properly. First, as mentioned earlier, route methods must always be chained together. Assigning intermediate steps to variables breaks the chain, preventing types from reaching hc. Second, when co-locating server and frontend in a monorepo, enable "strict": true inside compilerOptions in both tsconfig.json files. Omitting this prevents RPC types from resolving correctly.
While RPC is particularly powerful in custom development where a single team manages both server and frontend, different approaches are needed when collaborating with external frontend teams or when OpenAPI integration is required. The selection of type-sharing strategies is detailed in our article on type sharing from OpenAPI to oRPC, so choose the approach that best fits your project team structure.
Best Practices for Middleware, Error Handling, and Testing
Cross-cutting concerns should be delegated to middleware. Authentication, logging, and CORS are often covered by Hono built-ins or third-party middleware, while custom logic can be written type-safely using createMiddleware.
import { createMiddleware } from 'hono/factory'
const auth = createMiddleware<{ Variables: { userId: string } }>(
async (c, next) => {
const token = c.req.header('Authorization')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
c.set('userId', 'resolved-user-id')
await next()
}
)
Error handling is centralized using app.onError, catching anticipated errors by throwing HTTPException. This prevents handlers from being cluttered with try/catch while standardizing response structures in a single place.
import { HTTPException } from 'hono/http-exception'
app.onError((err, c) => {
if (err instanceof HTTPException) {
return c.json({ error: err.message }, err.status)
}
console.error(err)
return c.json({ error: 'InternalServerError' }, 500)
})
Testing is a major advantage of Hono. Applications can be executed directly using app.request() without spinning up an actual HTTP server. Paired with Vitest or similar tools, lightweight request-level test suites run exceptionally fast.
import { describe, it, expect } from 'vitest'
import app from '../src/app'
describe('users API', () => {
it('returns 400 on invalid body', async () => {
const res = await app.request('/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: '' }),
})
expect(res.status).toBe(400)
})
})
Since custom development frequently includes post-delivery maintenance, this capability to test behavior without running a server quietly provides a rock-solid foundation for preventing regressions.
Choosing Deployment Targets Based on "Who Owns Operations"
The final consideration is selecting the deployment target. Because identical Hono code runs anywhere, the sheer number of options can actually cause indecision. In custom development, rather than chasing absolute peak performance, it is practical to decide based on who owns operations and to what extent.
| Deployment Target | Suited projects | Important precautions |
|---|---|---|
| Cloudflare Workers | Public APIs at the edge with low latency and traffic spikes | Cannot use Node-specific APIs or certain libraries; execution time and bundle size limits |
| AWS Lambda | Existing AWS infrastructure, enterprise systems connecting to VPC resources | Cold starts; requires setting up infrastructure as code (IaC) |
| Node.js (Persistent) | Existing Node assets, co-located batch processes requiring persistent processes | Must manage server operations and scaling in-house |
| Bun | In-house/small scale prioritizing delivery speed | Some projects may lack track record and operational expertise |
While Workers is fast at the edge and handles scaling automatically, Node-specific APIs and certain libraries will not run, alongside execution time and bundle size constraints. For enterprise systems where existing assets reside in AWS and connect to databases within a VPC, Lambda fits naturally. Migration techniques for running Node-targeted applications on Lambda are covered in detail in our article on serverless migration with AWS Lambda Web Adapter, making it a great companion read when considering serverless migrations for legacy setups. For higher-level infrastructure decisions on whether to base systems on Cloudflare or AWS, see our article on infrastructure selection between Cloudflare and AWS.
In a project supported by GleamHub (a retail/e-commerce SaaS with a 3-person internal team, name withheld), we rebuilt a read-heavy product data delivery API using Hono and Cloudflare Workers. We adopted it for two reasons: delivering low-latency edge responses to end users worldwide, and sharing types via RPC to eliminate duplication since the same team owned the React frontend. As a result, production issues caused by frontend/API type mismatches—which occurred several times a month under Express—visibly dropped once type errors began halting builds. Meanwhile, write-heavy APIs dependent on Node-only payment SDKs were placed separately on Lambda rather than shoehorned into Workers. This pragmatic choice to avoid forcing everything into a single runtime was the practical secret to leveraging Hono's multi-runtime capability.
Getting Started with Hands-On Practice
Because Hono cleanly delivers a thin, fast foundation, you must take ownership of architectural decisions around structure, validation, type sharing, and deployment targets. Conversely, once those decisions are locked in, you break free from the exhaustion of rebuilding scaffolding on every project.
To get started hands-on, try migrating just one small existing API to Hono, exporting AppType on the server, and swapping the frontend over to the hc client. Experiencing the elimination of duplicate type definitions is the most immediate way to appreciate the benefits. From there, choosing whether to deploy to Workers or Lambda can be decided according to project-specific operational ownership.
If you are looking to redesign your custom backend with Hono or migrate existing APIs to an edge/serverless architecture, please get in touch via the GleamHub contact form. After reviewing your current architecture and infrastructure prerequisites, we will work with you to nail down the foundational design and deployment selection.
Sources
- Personal Best Practices for Building API Backends with Hono — Zenn (ashunar0)
- RPC — Hono Official Documentation
- Validation — Hono Official Documentation
- Third-party Middleware — Hono Official Documentation
- Hono.js in 2026: The Fastest Web Framework for Cloudflare Workers - DEV Community
- hono/zod-validator - npm









