Hono
Hono Recipe
Section titled “Hono Recipe”Hono is fetch-native, so it mounts the HTTP transport core directly: .mount() strips the prefix before the handler sees the request. Nothing to install beyond uql-orm.
import { Hono } from 'hono';import { cors } from 'hono/cors';import { createFetchHandler } from 'uql-orm/http';import { pool } from './uql.config.js';import { Post, User } from './shared/models/index.js';
const handler = createFetchHandler({ pool, include: [User, Post] });
const app = new Hono();app.use('*', cors());app.get('/health', (c) => c.text('ok'));app.post('/checkout', async (c) => c.json(await runCheckout(c.req.raw))); // custom business logicapp.mount('/api', handler); // entity CRUD under /api
export default app; // Bun and Workers; Deno.serve(app.fetch), or serve(app) from @hono/node-serverThat serves the full wire protocol per entity, QUERY included, since .mount() forwards every method. It claims only the /api prefix, so your own routes and middleware sit beside the generated CRUD; keep them for read-modify-write logic, multi-entity transactions, aggregations, uploads and streaming. Unknown routes under the prefix 404 from the handler rather than falling through.
Your own routes
Section titled “Your own routes”The mount is optional. pool is a plain ORM, so any route can query it:
app.get('/posts', async (c) => { const posts = await pool.findMany(Post, { $where: { published: true }, $limit: 20, }); return c.json(posts);});Work spanning several statements goes in pool.transaction, which hands you the querier to run all of them on.
Authorization and tenant scoping
Section titled “Authorization and tenant scoping”Nothing here is Hono-specific, so the core documents it once: getContext takes the web Request and scopes every query in it through a security filter, and the hooks shape a response, stamp a field or reject a payload. A hook that throws with a numeric status becomes that HTTP status.
Runtimes
Section titled “Runtimes”The same handler runs unchanged on Bun, Deno and Node. D1 is the exception: its binding arrives with the request, and .mount() hands the handler a bare Request with no way back to c.env. Route it yourself and pass the prefix as basePath:
import { Hono } from 'hono';import { type D1Database, D1QuerierPool } from 'uql-orm/d1';import { createFetchHandler } from 'uql-orm/http';import { Post, User } from './shared/models/index.js';
const app = new Hono<{ Bindings: { DB: D1Database } }>();
app.all('/api/*', (c) => { const handler = createFetchHandler({ pool: new D1QuerierPool(c.env.DB), include: [User, Post], basePath: '/api', }); return handler(c.req.raw);});D1 has no transactions, so its write routes cannot run; reads and single-statement writes do.
Hono’s own RPC client infers from chained routes, so hc cannot see mounted CRUD: the typed client for it is UQL’s browser client, which sends the same query you write on the server. For per-procedure contracts, see tRPC and oRPC.