Skip to content
rest-rpc
Esc
navigateopen⌘Jpreview
On this page

Hono

Register contract handlers in a Hono app.

Use @rest-rpc/hono to implement and register contract routes in a Hono app.

The adapter registers routes by HTTP method and path, then delegates validation and response handling to the shared server layer.

Install

pnpm add @rest-rpc/core @rest-rpc/hono hono

Basic Setup

import { registerRoutes, router } from "@rest-rpc/hono";
import { Hono } from "hono";
import { api } from "./contract";

const app = new Hono();

const routes = router(api, {
	todos: {
		get({ id, context }) {
			const userAgent = context.c.req.header("user-agent");
			void userAgent;

			const todo = todos.get(id);

			if (!todo) {
				return {
					status: 404,
					body: {
						code: "TODO_NOT_FOUND",
					},
				};
			}

			return todo;
		},
	},
});

registerRoutes(app, routes);

export default app;

Handler Input

Handlers receive flattened request input where context contains the Hono context object.

get({ id, "x-request-id": requestId, context }) {
	return loadTodo({
		id,
		requestId,
		c: context.c,
	});
}

Handler Returns

Handlers can always return an explicit response envelope.

return {
	status: 404,
	body: {
		code: "TODO_NOT_FOUND",
	},
};

If a route has exactly one successful response, the handler can return the success body directly.

return todo;

You can also use ContractResponseError to throw a declared error response when that’s more convenient.

throw new ContractResponseError(api.todos.get, {
	status: 404,
	body: {
		code: "TODO_NOT_FOUND",
	},
});

Error Handling

Use errorHandlers to customize server-generated error responses. Declared responses returned from handlers, including ContractResponseError, do not run through these hooks.

registerRoutes(app, routes, {
	errorHandlers: {
		onRequestValidationError: ({ issues }) => ({
			status: 422,
			body: { code: "VALIDATION_ERROR", issues },
		}),
		onUnhandledError: () => ({
			status: 500,
			body: { code: "INTERNAL_SERVER_ERROR" },
		}),
	},
});

Middleware

rest-rpc does not own middleware. Use Hono middleware the same way you would in any Hono app.

When middleware needs to behave differently for different contract routes, use createRouteMatcher to match the incoming request back to the contract route.

const matchContractRoute = createRouteMatcher(api);

app.use(async (c, next) => {
	const match = matchContractRoute({
		method: c.req.method,
		path: c.req.path,
	});

	if (!match?.route.metadata?.requiresAuth) {
		return next();
	}

	const session = await loadSession(c.req.header("authorization"));

	if (!session) {
		return c.json({ code: "UNAUTHORIZED" }, 401);
	}

	c.set("session", session);
	return next();
});

Handlers can read values added by middleware through context.c.

const routes = router(api, {
	todos: {
		get({ id, context }) {
			return loadTodoForSession({
				id,
				session: context.c.get("session"),
			});
		},
	},
});

Body Parsing

For normal JSON routes, the adapter can read JSON request bodies through Hono’s request APIs.

Use parseBody when your app needs custom body parsing.

import { isCustomBody } from "@rest-rpc/core";

registerRoutes(app, routes, {
	parseBody: async ({ c, body }) => {
		if (isCustomBody(body)) {
			return c.req.arrayBuffer();
		}

		return c.req.json();
	},
});

WebSockets

Hono gets upgradeWebSocket from the Hono runtime adapter. Create the Hono app normally, import upgradeWebSocket from your runtime, then pass both to registerRoutes.

beforeUpgrade runs after request validation and before the socket is accepted. Return an upgrade rejection to deny the connection, or read and attach data to context.c for the WebSocket handler.

import { Hono } from "hono";
import { upgradeWebSocket } from "hono/cloudflare-workers";
import { registerRoutes } from "@rest-rpc/hono";

const honoApp = new Hono();

registerRoutes(honoApp, routes, {
	webSocket: {
		upgradeWebSocket,
		beforeUpgrade({ context, route, request }) {
			context.c;
			route;
			request;
			return undefined;
		},
	},
});

See WebSockets for the contract and handler model.

Was this page helpful?