Skip to content

Hono

Hono is fetch-native, so it mounts UQL’s HTTP transport core directly. createFetchHandler returns a web-standard (request: Request) => Promise<Response>, and .mount() binds it to a prefix and strips that prefix before the handler sees the request. There is nothing to install beyond uql-orm.

import { Hono } from 'hono';
import { createFetchHandler } from 'uql-orm/http';
import './uql.config.js'; // setQuerierPool + entity imports
import { User, Post } from './shared/models/index.js';
const handler = createFetchHandler({ include: [User, Post] });
const app = new Hono();
app.mount('/api', handler);
export default app; // Bun.serve, Deno.serve, Cloudflare Workers, Node via @hono/node-server

This mounts the full wire protocol for each entity (list, get, count, create, upsert, bulk operations, delete) plus the QUERY transport. Because .mount() forwards every method to the handler, the QUERY method works here without extra routing.

.mount() only claims the /api prefix, so your own Hono routes and middleware live side by side with the generated CRUD. Keep hand-written routes for read-modify-write logic, multi-entity transactions, aggregations, file uploads, and streaming:

import { cors } from 'hono/cors';
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

createFetchHandler accepts the core’s pre, preSave, preFilter, and post hooks. The hook context is the web Request, so read auth and tenant state from its headers:

const handler = createFetchHandler({
include: [User, Post],
async preFilter({ query, context }) {
// context is the web Request; abort by throwing with a numeric status
const user = await authenticate(context.headers.get('authorization'));
if (!user) {
throw Object.assign(new Error('unauthorized'), { status: 401 });
}
query.$where ??= {};
Object.assign(query.$where, { creatorId: user.id });
},
});