Skip to content

Hono

Hono is fetch-native, so it mounts the HTTP transport core directly: .mount() binds the handler to a prefix and strips that prefix before it 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 './uql.config.js'; // setQuerierPool + entity imports
import { Post, User } from './shared/models/index.js';
const handler = createFetchHandler({ include: [User, Post] });
const app = new Hono();
app.use('*', cors());
app.get('/health', (c) => c.text('ok'));
app.post('/checkout', (c) => c.json(runCheckout(c.req))); // custom business logic
app.mount('/api', handler); // entity CRUD under /api
export default app; // Bun.serve, Deno.serve, Cloudflare Workers, Node via @hono/node-server

That serves the full wire protocol per entity. Because .mount() forwards every method, the QUERY transport works with no extra routing. .mount() 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.

getContext receives the web Request and returns the ambient context for the whole request, so a security filter scopes every query it runs, non-bypassable from the wire and fail-closed:

const handler = createFetchHandler({
include: [User, Post],
async getContext(request) {
const user = await authenticate(request.headers.get('authorization'));
if (!user) {
throw Object.assign(new Error('unauthorized'), { status: 401 }); // numeric status becomes the HTTP status
}
return { tenantId: user.tenantId, userId: user.id };
},
});

The core’s hooks cover everything else: shaping a response, stamping a field, rejecting a payload.