Node + TypeScript API (Hono)

Pathrule2 Rules • 2 Memories • 1 Skill

An opinionated baseline for shipping Hono APIs on Node 22 with end-to-end type safety. It covers route chaining for RPC inference, Standard Schema validation at the edge of every handler, centralized error handling with HTTPException, and the conventions that keep the client and server in sync.

Suggested path map

Pathrule places each piece on the matching path, so your assistant only sees it where it belongs. This is the scoping you get on import; you can adjust it in your workspace.

/ workspace root
node-ts-api-hono-review
src/
Validate every input and centralize errors
Hono baseline: versions, runtime, and app wiring
routes/
Chain routes and export AppType for RPC
client/
Typed RPC client usage

Rules

2
Chain routes and export AppType for RPC/src/routeshighstrictAlways chain .get/.post/.route calls and export typeof the chained app so the RPC client infers types.
1The Hono RPC client only sees routes that are part of a single chained expression. Break the chain and `hc<AppType>` resolves responses to `unknown`.
2 
3- Define handlers by chaining directly off `new Hono()` (for example `const app = new Hono().get(...).post(...)`); do not assign `app` then call `app.get(...)` on later lines.
4- Mount sub-apps inline with `.route('/books', books)` inside the same chain, never as a standalone `app.route(...)` statement.
5- Export the route tree as a type with `export type AppType = typeof routes` and import it where you build the client.
6- Skip Rails-style controller files; write handlers right after the path so path-param and validator types infer. If you must split, use `factory.createHandlers()`.
Validate every input and centralize errors/srchighstrictGuard each handler with a Standard Schema validator and route all failures through app.onError with HTTPException.
1Untyped `c.req.json()` and raw query params are untrusted input. Every handler reads validated data, and every failure returns one consistent shape.
2 
3- Validate `json`, `query`, `param`, `form`, and `header` targets with `zValidator` or the multi-library `sValidator` (`@hono/standard-validator`), then read with `c.req.valid('json')`.
4- Return a 400 with a structured body from the validator hook on `!result.success`; do not let invalid data reach business logic.
5- Throw `HTTPException` for expected failures (`new HTTPException(404, { message })`) instead of returning ad-hoc error objects.
6- Register a single `app.onError((err, c) => ...)` that serializes `HTTPException` via `err.getResponse()` and maps everything else to a 500 without leaking stack traces.

Memories

2
Hono baseline: versions, runtime, and app wiring/srcPinned stack and the canonical app/server bootstrap for Hono on Node 22.
1This API runs Hono v4 (4.12.x line) on Node 22 LTS with TypeScript 5.4+, which the route-param type inference depends on.
2 
3- Serve on Node with `@hono/node-server`'s `serve({ fetch: app.fetch, port })`; the same `app` deploys unchanged to Workers, Deno, or Bun because Hono targets Web Standards.
4- Keep `src/index.ts` thin: create the root app, attach global middleware (`logger`, `cors`, `secureHeaders`), mount feature routers via chained `.route()`, then `export default app` plus `export type AppType`.
5- Use `c.json()`, `c.text()`, and typed `c.var`/context variables instead of mutating Node req/res directly.
6- Order middleware deliberately: auth and validation run before handlers; `onError` and `notFound` are registered once on the root app.
Typed RPC client usage/src/clientHow to consume the server's AppType with hc and avoid the unknown-response trap.
1The frontend and internal callers talk to the API through Hono's RPC client, which derives request and response types straight from `AppType` with no codegen step.
2 
3- Build the client with `import { hc } from 'hono/client'` and `const client = hc<AppType>(baseUrl)`; types come from the exported server type, so import it as `import type { AppType }`.
4- Call endpoints as method chains, for example `await client.books[':id'].$get({ param: { id } })`, then `await res.json()` is fully typed from the handler's `c.json()` return.
5- If responses come back as `unknown`, the cause is almost always a server route mounted outside the chain, not a client bug; fix the chaining on the server.
6- For large apps, compile the client type once with `hc<AppType>` and re-export it to cut editor type-checking cost (`hcWithType` pattern).

Skills

1
node-ts-api-hono-review/rootPre-merge checklist for Hono API changes: RPC type safety, validation, errors, and runtime.
1---
2name: node-ts-api-hono-review
3description: Review checklist for Node + TypeScript Hono API changes. Run before merging any route, validator, or client change to keep RPC types, validation, and error handling consistent.
4---
5 
6# Node + TypeScript API (Hono) review
7 
8- [ ] Routes are defined as a single chained expression (`new Hono().get(...).post(...)`), not reassigned line by line.
9- [ ] Sub-apps are mounted inline with `.route()` inside the chain, and `export type AppType = typeof routes` is present.
10- [ ] Every handler reads input via `c.req.valid(...)` behind a `zValidator`/`sValidator`, never raw `c.req.json()` or query params.
11- [ ] Validator failure paths return a structured 400; no invalid data reaches business logic.
12- [ ] Expected failures throw `HTTPException`; a single `app.onError` serializes errors and hides stack traces on 500.
13- [ ] Global middleware (`logger`, `cors`, `secureHeaders`) and `notFound` are registered once on the root app in the right order.
14- [ ] Pinned to Hono v4 on Node 22 LTS with TypeScript 5.4+; Node entry uses `@hono/node-server` `serve()`.
15- [ ] RPC client uses `hc<AppType>` with `import type`, and responses type-check (no `unknown` leaks).

Why this pattern

AI agents writing Hono APIs break RPC type inference, scatter validation, and return inconsistent error shapes.

Built for Backend teams building type-safe Hono APIs on Node or the edge.

Keeps your assistant from:

  • Mounting sub-apps on separate lines so the RPC client infers responses as unknown
  • Reading untrusted req.json() or query params without a schema validator
  • Returning ad-hoc error objects instead of a consistent HTTPException shape
License
Apache-2.0
Version
1.0.0
Updated
2026-06-09
View source