Skip to content
NewFrom Mongoose to SQL6 min read

Hono

Your Hono routes query the pool directly. If you also want CRUD routes for many entities without writing them, mount the HTTP handler. Hono is fetch-native, so nothing else is needed.

import { Hono } from 'hono';
import { Post } from './shared/models/index.js';
import { pool } from './uql.config.js';
const app = new Hono();
app.get('/posts', async (c) => {
const posts = await pool.findMany(Post, {
$where: { published: true },
$limit: 20,
});
return c.json(posts);
});
export default app; // Bun and Workers; on Node, serve(app) from @hono/node-server

The pool shows the uql.config.ts it comes from. Run several writes together in pool.transaction.

Mount the handler under a prefix, beside your own routes:

import { cors } from 'hono/cors';
import { createFetchHandler } from 'uql-orm/http';
import { Post, User } from './shared/models/index.js';
const handler = createFetchHandler({ pool, include: [User, Post] });
app.use('*', cors());
app.mount('/api', handler);

.mount() strips the prefix and forwards every method, so this serves the wire protocol, QUERY included. An unknown route under /api is a 404 from the handler, not passed on to later routes.

Hooks and tenant scoping are the handler’s: see hooks and getContext. Their context is the web Request.

Hono’s hc client sees only chained routes, not mounted ones. Use the browser client for the CRUD routes.

A D1 binding arrives with each 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 { 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, and the handler runs every write in one, so the write routes answer 400 on D1. Reads work. Write through your own routes instead.