Skip to content

Hono Setup

This guide explains how to integrate Toapi with Hono, a fast, lightweight, web-standard web framework.

Terminal window
npm install @toapi/server @toapi/client hono zod
# or
pnpm add @toapi/server @toapi/client hono zod

Create a central file to define your API structure.

src/api.ts
import { defineApi } from "@toapi/server";
export const api = defineApi();
// We'll add routes here later
// .route("/hello", import("./routes/hello"))

Mount the Toapi request handler onto a Hono application instance. Since createRequestHandler works with standard Web API Request and Response objects, it integrates seamlessly with Hono.

src/index.ts
import { Hono } from "hono";
import { createRequestHandler } from "@toapi/server";
import { api } from "./api";
const app = new Hono();
// Initialize the Toapi handler
// ensure basePath matches the route you mount it on
const handler = createRequestHandler(api, {
basePath: "/api",
});
// Mount the handler on the /api prefix
// The '*' wildcard ensures all subpaths are captured
app.all("/api/*", (c) => {
return handler(c.req.raw);
});
export default app;

If you are using a runtime like Bun, Deno, or Cloudflare Workers, export default app is usually a sufficient entry point.

To consume your API, create the typed client.

src/client.ts
import { createFetchClient } from "@toapi/client";
import type { api } from "./api";
// Adjust the URL to match your server's address
export const client = createFetchClient<typeof api.routes>("http://localhost:3000/api");

Create a route handler file. For example, src/routes/hello.ts:

src/routes/hello.ts
import { defineHandler, TResponse } from "@toapi/server";
export const GET = defineHandler({ authorize: () => true }, async () => {
return TResponse.json({ message: "Hello from Toapi on Hono!" });
});

Register it in src/api.ts:

src/api.ts
import { defineApi } from "@toapi/server";
export const api = defineApi().route("/hello", import("./routes/hello"));

Now you can start your Hono server and use the client to make requests.

src/example-usage.ts
import { client } from "./client";
async function main() {
const response = await client.hello.get();
console.log(response.message); // "Hello from Toapi on Hono!"
}
main();

If you need to access Hono’s Context (like environment variables in Cloudflare Workers) inside your Toapi handlers, you currently need to pass them via a custom storage mechanism (like AsyncLocalStorage) or by extending the request object before passing it to handler, since Toapi abstracts away the underlying framework’s specific context object in favor of a standard TRequest.

To enable tag-based revalidation across all cache layers, expose the invalidation stream. defineApi automatically creates a PubSub instance, so no extra setup is needed for single-host deployments.

The catch-all handler already serves the stream at /api/__tapi/invalidations. If you prefer a dedicated route, add /api/revalidate to your Hono app:

src/index.ts
import { Hono } from "hono";
import { createRequestHandler, streamRevalidatedTags } from "@toapi/server";
import { api } from "./api";
const app = new Hono();
const handler = createRequestHandler(api, {
basePath: "/api",
});
app.get("/api/revalidate", (c) => {
return streamRevalidatedTags({
cache: api.cache,
});
});
app.all("/api/*", (c) => {
return handler(c.req.raw);
});
export default app;

The revalidation route is registered before the catch-all so it doesn’t get swallowed by the Toapi handler.

For setting up a service worker that connects to this endpoint, see the @toapi/worker package. For details on how the cache layers interact, see Caching Strategies.