Hono
Integrate Hono on Node and Web runtimes with route mounting, authentication, CORS, compatible databases, ciphertext routes, and deployment guidance.
@ownfold/hono adapts Hono’s raw Web Request to Ownfold’s standard fetch handler. The adapter is
small because Hono already uses Web Platform request and response types.
Database and identity are separate choices. Select both in the
framework recipe builder, then pass the resulting VaultServer
to the same Hono handler.
Install packages
pnpm add @ownfold/hono@beta @ownfold/server@beta hono
Add an authentication resolver and database adapter for your runtime. The build-tested Node example
uses Better Auth and PostgreSQL in examples/hono-better-auth-postgres.
This is a complete backend integration. Add @ownfold/browser, @ownfold/fetch, or
@ownfold/react only if a separate browser client needs them.
Create the server
import { betterAuthUserResolver } from "@ownfold/better-auth"
import { nodePostgresVaultAdapter } from "@ownfold/postgres/node-postgres"
import { createVaultServer } from "@ownfold/server"
import { auth } from "./auth"
import { pool } from "./database"
export const vaultServer = createVaultServer({
adapter: nodePostgresVaultAdapter(pool),
getUserId: betterAuthUserResolver(auth),
})
The resolver receives context.req.raw through the Ownfold handler. Session cookies, bearer
headers, and other authentication inputs remain owned by the host auth system. The resolver returns
only the stable user ID.
Mount the handler
import { createHonoVaultHandler } from "@ownfold/hono"
import { Hono } from "hono"
import { vaultServer } from "./vault-server"
const app = new Hono()
const ownfold = createHonoVaultHandler({
server: vaultServer,
isOriginAllowed: ({ request }) => {
const origin = request.headers.get("origin")
return origin === null || origin === process.env.APP_ORIGIN
},
})
app.all("/api/ownfold/*", (context) => ownfold(context))
export default app
Register middleware before the route. Hono resolves matching handlers in registration order. A generic wildcard or early handler mounted first can consume the request before Ownfold sees it.
createHonoVaultHandler() returns a normal Hono Handler. It forwards context.req.raw and
returns the handler’s Response; it does not read Hono variables or select an authenticated user
from route params.
Browser transport
This section is optional. A backend-only Hono application can stop after mounting the handler.
import { createVaultClient } from "@ownfold/browser"
import { createFetchVaultTransport } from "@ownfold/fetch"
export const vault = createVaultClient({
transport: createFetchVaultTransport({
baseURL: "/api/ownfold",
}),
})
For a separate frontend origin, set credentials: "include" and configure credentialed CORS on
the Hono application. CORS must run before Ownfold routes:
import { cors } from "hono/cors"
app.use(
"/api/ownfold/*",
cors({
origin: "https://app.example.com",
allowMethods: ["GET", "POST", "OPTIONS"],
allowHeaders: ["Content-Type"],
credentials: true,
maxAge: 600,
}),
)
CORS response headers do not replace CSRF/origin validation. Keep isOriginAllowed, cookie
SameSite policy, and the auth provider’s trusted-origin checks aligned.
Runtime compatibility
Hono runs on Node.js, Bun, Deno, Cloudflare Workers, and other Web runtimes. The Hono adapter is portable; the rest of your selected stack may not be.
| Runtime | Suitable adapters | Important constraint |
|---|---|---|
| Node.js | PostgreSQL, Drizzle, Prisma, SQLite | Select drivers compatible with your Node version and deployment model. |
| Bun | Web-compatible transports and supported DB drivers | Verify ORM/driver and native-module support independently. |
| Cloudflare Workers | Custom HTTP/database adapter or runtime-native driver | node:sqlite, ordinary pg pools, and many Node auth packages are unavailable. |
| Deno | Web-compatible custom adapters | Do not assume Node package or native-addon compatibility. |
Ownfold’s core contracts are runtime-neutral. Do not polyfill an incompatible database driver into an edge worker merely to reuse an official Node adapter; implement the narrow adapter contract for the runtime and run the shared compliance suite.
Ciphertext-only application routes
Ownfold coordination endpoints do not store application notes. Use a separate Hono route:
app.put("/api/notes/:id", async (context) => {
const recordId = context.req.param("id")
const body: unknown = await context.req.json()
const validated = await vaultServer.validateEncryptedRecordWrite({
request: context.req.raw,
body,
namespace: "notes",
recordId,
})
if (validated.status === "error") {
return context.json(
{ code: validated.error.code, message: validated.error.message },
mapOwnfoldStatus(validated.error),
)
}
const userId = await requireUserId(context.req.raw)
await noteRepository.upsert({
userId,
recordId,
encryptedPayload: validated.value.encryptedPayload,
})
return context.body(null, 204)
})
Derive namespace in server code and recordId from the canonical route. The browser uses the same
values and authenticated user ID during encryption. Never persist a parallel plaintext title,
preview, search field, or analytics property.
Error handling
The Ownfold handler returns structured protocol errors itself. A global app.onError() handles
unexpected application failures, but it must not log request bodies, response bodies, Recovery Kit
content, or decrypted values.
app.onError((error, context) => {
logger.error({
errorName: error.name,
requestId: context.get("requestId"),
route: context.req.path,
})
return context.json({ code: "INTERNAL_ERROR" }, 500)
})
Expected Ownfold failures remain normal HTTP responses and should not be rethrown into this handler.
Production checklist
- Mount auth/session middleware before Ownfold if the provider requires it.
- Mount CORS before Ownfold and use an exact allowlist.
- Set body limits at the platform boundary; Ownfold additionally enforces its protocol limit.
- Disable response caching for coordination and private-record routes.
- Use HTTPS and secure cookies outside local development.
- Close database pools during Node process shutdown.
- Verify the deployed runtime supports the chosen auth and database packages.
- Scrub request bodies from Hono logging middleware and observability exporters.
Troubleshooting
The handler returns 404 for every Ownfold path
Mount the wildcard as /api/ownfold/*, keep the transport base URL /api/ownfold, and ensure an
earlier wildcard handler is not consuming the request.
Authentication works elsewhere but Ownfold returns 401
Verify the session cookie/header reaches context.req.raw and the auth middleware runs before
the Ownfold route. Never inject a body userId as a workaround.
Cross-origin POST fails while GET succeeds
Check preflight ordering, allowed methods/headers, credentialed cookie settings, exact origin,
and isOriginAllowed. CORS and origin validation must both pass.
A Worker deployment fails to bundle
Inspect the database and authentication packages, not only @ownfold/hono. Replace Node-only
dependencies with runtime-native implementations behind Ownfold’s contracts.
Complete example
examples/hono-better-auth-postgres demonstrates a production-buildable Node service with Better
Auth, PostgreSQL coordination, encrypted-note routes, connection limits, TLS, and graceful
shutdown.