The @noverax/courier-hono adapter lets you host the Courier proxy route on any Hono-supported runtime: Node.js, Bun, Deno, and Cloudflare Workers.
In one command
npx @noverax/create-courierIn a Hono server it adds routes for the proxy and a page at /courier, mounted on your app, with a typed example API and its mock. See Create Courier.
Installation
npm install @noverax/courier @noverax/courier-hono honoDefine your API sources
import { courier, defineApi, defineGroup } from "@noverax/courier/define"
const { endpoint } = courier()
type User = { id: number; name: string; email: string }
const users = defineGroup({
prefix: "users",
endpoints: {
getAll: endpoint.get("").response<User[]>(),
getById: endpoint.get(":id").variables<{ id: number }>().response<User>(),
create: endpoint.post("").variables<{ name: string; email: string }>().response<User>(),
},
})
export const api = defineApi({
baseURL: process.env.API_URL ?? "http://localhost:4000",
groups: { users },
})
export const sources = { myApi: api }Setup
Create a Hono app with the Courier handler:
import { Hono } from "hono"
import { cors } from "hono/cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-hono"
import { sources } from "./lib/sources"
registerSources(sources)
const app = new Hono()
app.use("/api/*", cors())
app.all("/api/request", courierHandler())
export default appThat is all you need. The courierHandler function returns a standard Hono handler that processes incoming Courier proxy requests and forwards them to your backend.
Runtime-specific entry points
Node.js
import { serve } from "@hono/node-server"
import { Hono } from "hono"
import { cors } from "hono/cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-hono"
import { sources } from "./lib/sources"
registerSources(sources)
const app = new Hono()
app.use("/api/*", cors())
app.all("/api/request", courierHandler())
serve({ fetch: app.fetch, port: 3001 }, (info) => {
console.log(`Server running on http://localhost:${info.port}`)
})Bun
import { Hono } from "hono"
import { cors } from "hono/cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-hono"
import { sources } from "./lib/sources"
registerSources(sources)
const app = new Hono()
app.use("/api/*", cors())
app.all("/api/request", courierHandler())
export default {
port: 3001,
fetch: app.fetch,
}Deno
import { Hono } from "hono"
import { cors } from "hono/cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-hono"
import { sources } from "./lib/sources.ts"
registerSources(sources)
const app = new Hono()
app.use("/api/*", cors())
app.all("/api/request", courierHandler())
Deno.serve({ port: 3001 }, app.fetch)Cloudflare Workers
import { Hono } from "hono"
import { cors } from "hono/cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-hono"
import { sources } from "./lib/sources"
registerSources(sources)
const app = new Hono()
app.use("/api/*", cors())
app.all("/api/request", courierHandler())
export default appSame handler, any runtime
The courierHandler function uses the standard Web API Request/Response objects, so it works identically across all Hono-supported runtimes with zero configuration changes.
Adding other routes
The Courier handler only handles the proxy route. You can add any other Hono routes alongside it:
import { Hono } from "hono"
import { cors } from "hono/cors"
import { registerSources } from "@noverax/courier"
import { courierHandler } from "@noverax/courier-hono"
import { sources } from "./lib/sources"
registerSources(sources)
const app = new Hono()
app.use("/api/*", cors())
// Courier proxy route
app.all("/api/request", courierHandler())
// Your own routes
app.get("/health", (c) => c.json({ status: "ok" }))
app.get("/api/stats", (c) => {
return c.json({ uptime: process.uptime() })
})
export default appThe user's session
An API that keeps a session gets the user's session with every call a route, a service or a step it awaits makes, once the request is served with it at hand:
import { requestContext } from "@noverax/courier-hono/request-context"
app.use(requestContext())Add it before the routes that call APIs. The session cookies the API sets reach the browser on the response the route answers with, beside the route's own — on a fetch's response too. The request is kept in AsyncLocalStorage, which Node, Bun and Deno provide, and Cloudflare Workers with the nodejs_compat flag; the main entry imports none of it, so an application that keeps no session runs anywhere Hono does. The user's address is the connection's where the runtime reports one. Hono 4 is checked on @hono/node-server. See User Sessions.
Studio integration & appOrigin
Wire your Hono app into Studio with a single attachRuntime() call. In development it loads courier.config.ts from disk, registers its sources, and opens the connection to Studio; in production the app's own registerSources() call registers them. The appOrigin field for Playground proxy SSE/WS resolution lives in courier.config.ts.runtime.appOrigin (or set the COURIER_APP_URL env var to override per-environment).
import { serve } from "@hono/node-server"
import { Hono } from "hono"
import { courierHandler } from "@noverax/courier-hono"
if (process.env.NODE_ENV !== "production") {
const { attachRuntime } = await import("@noverax/courier-studio-core/runtime")
await attachRuntime()
}
const app = new Hono()
app.all("/api/request", courierHandler())
serve({ fetch: app.fetch, port: 3001 })Alternatively, set COURIER_APP_URL instead of pinning appOrigin in the config:
COURIER_APP_URL=http://localhost:3001 node src/index.jsEdge runtimes
attachRuntime() only runs on Node-compatible runtimes (Bun also works). Cloudflare Workers and Deno do not currently host Studio's Socket.IO bridge — for those targets the Studio integration is a no-op and Playground proxy mode is unavailable. Use the standalone server for development against edge deployments.
No magic default
There is no localhost:3001 auto-fallback. If appOrigin is missing, clicking "Subscribe" on an SSE/WS endpoint in Studio Playground proxy mode throws ConfigurationError("APP_ORIGIN_NOT_CONFIGURED") with a pointer back to this section. The correct value depends on your runtime — custom port, Docker host, HTTPS, reverse proxy — and a wrong guess would silently route requests to nowhere.
Client-side setup
On the client, point your caller to this server's proxy URL. Choose the guide for your frontend:
- React (Vite / CRA) —
createProxyQueryCallerwith React Query hooks - Vue —
createProxyCallerwith composables - Next.js — full-stack with
apiRouteandcreateProxyQueryCaller - Nuxt — full-stack with
courierHandlerandcreateProxyCaller
import { createProxyCaller } from "@noverax/courier"
import type { api } from "../shared/sources"
export const caller = createProxyCaller<typeof api>("api", {
proxyPath: { http: "http://localhost:3001/api/request" },
})