# Hono

Hono server adapter using courierHandler — works on Node.js, Bun, Deno, and Cloudflare Workers.

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

```bash
npx @noverax/create-courier
```

In 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](/docs/create-courier).

## Installation

```bash
npm install @noverax/courier @noverax/courier-hono hono
```

## Define your API sources

```ts title="lib/sources.ts"
import { courier, defineApi, defineGroup } from "@noverax/courier/define"

const { endpoint } = courier()

type User = { id: number; name: string; email: string }

const users = defineGroup({
  prefix: "users",
  // A page may call these through the proxy; an endpoint no mark opens stays the server's
  expose: true,
  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:

```ts title="src/index.ts"
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 app
```

That 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

```ts title="src/index.ts"
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

```ts title="src/index.ts"
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

```ts title="src/index.ts"
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

```ts title="src/index.ts"
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 app
```

> **Same 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:

```ts title="src/index.ts"
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 app
```

## The 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:

```ts title="src/index.ts"
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](/docs/server-side/session).

## Studio integration & `appOrigin` <a id="studio-app-origin"></a>

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. Studio's Playground sends its proxy calls to the app's origin, which in development the app learns from the server itself — the socket the first request to a Courier route arrived on — so open a page that calls your API before the first Playground call. Where Studio cannot see the server, set `courier.config.ts.runtime.appOrigin` or the `COURIER_APP_URL` env var — see [How the app learns its origin](/docs/api/config#app-origin).

```ts title="src/index.ts"
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:

```bash
COURIER_APP_URL=http://localhost:3001 node src/index.js
```

> **Edge 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](/docs/deployment/standalone-server) for development against edge deployments.

> **No magic default**
>
> There is no `localhost:3001` auto-fallback. Until the config, the environment or the server itself names the origin, a Playground call in proxy mode throws `ConfigurationError("APP_ORIGIN_NOT_CONFIGURED")` with a pointer to [how the app learns its origin](/docs/api/config#app-origin). 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)](/docs/frameworks/react) — `createProxyQueryCaller` with React Query hooks
- [Vue](/docs/frameworks/vue) — `createProxyCaller` with composables
- [Next.js](/docs/frameworks/nextjs) — full-stack with `apiRoute` and `createProxyQueryCaller`
- [Nuxt](/docs/frameworks/nuxt) — full-stack with `courierHandler` and `createProxyCaller`

The caller takes the routes Courier writes into `courier.routes.ts` from the endpoints your sources mark `.expose()`, never the definition itself — see [Exposing Endpoints](/docs/proxy-mode/expose):

```ts title="lib/caller.ts"
import { createProxyCaller } from "@noverax/courier"
import { myApiRoutes } from "../courier.routes"

export const caller = createProxyCaller(myApiRoutes, {
  proxyPath: { http: "http://localhost:3001/api/request" },
})
```
