Fetch Runtime Integration
Use this recipe when you want to host hypequery routes inside any platform that exposes a Fetch-style handler: Hono, Cloudflare Workers, Deno, Bun, Remix, etc. The flow is always the same—define your analytics catalog once, wrap api.handler with the fetch adapter, and mount it under the route prefix you control.
Prerequisites
- A Fetch-compatible runtime (examples below use Hono +
@hono/node-server) @hypequery/clickhouse,@hypequery/serve,zod- ClickHouse credentials exposed via
CLICKHOUSE_*env vars
1. Set up your Fetch runtime
Install and scaffold Hono (or skip if you already have an app):
pnpm add hono @hono/node-server
// app.ts
import { Hono } from "hono";
export const app = new Hono();
app.get("/", (c) => {
return c.json({
status: "ok",
runtime: "node",
});
});
app.get("/hello/:name", async (c) => {
const name = c.req.param("name");
return c.text(`Hello ${name}!`);
});
// index.ts
import "dotenv/config";
import { serve } from "@hono/node-server";
import { app } from "./app.js";
const port = process.env.PORT
? Number(process.env.PORT)
: 3000;
serve({
fetch: app.fetch,
port,
});
console.log(`🚀 Hono running on http://localhost:${port}`);
2. Define queries and routes
Follow the typical hypequery setup either manually or via hypequery init. Create analytics/queries.ts. Set basePath: '' if you want to mount each route manually, or keep /api/analytics if you prefer a shared prefix.
import { initServe } from '@hypequery/serve';
import { db } from './client';
const serve = initServe({
basePath: '/',
context: () => ({ db }),
});
const { query } = serve;
export const api = serve.define({
queries: serve.queries({
tripsQuery: query
.describe('Example query using the trips table')
.query(async ({ ctx }) =>
ctx.db
.table('trips')
.select('*')
.limit(1)
.execute()
),
}),
});
api.route('/tripsQuery', api.queries.tripsQuery)
Want
/api/hypequery/tripsQueryinstead? SetbasePath: '/api/hypequery'ininitServe. Everyapi.route()inherits that prefix, so your fetch handler can mount once at/api/hypequery/*.
3. Mount the fetch adapter
// app.ts
import { Hono } from "hono";
import { api } from "./analytics/queries.js";
import { createFetchHandler } from "@hypequery/serve";
const hypequery = createFetchHandler(api.handler);
export const app = new Hono();
app.get("/", (c) => {
return c.json({
status: "ok",
runtime: "node",
});
});
app.get("/trips", async (c) => {
// call in process if you like
const result = await api.run('tripsQuery');
return c.json(result)
})
// Wire /tripsQuery into Hono’s router
app.all('/tripsQuery', (c) => hypequery(c.req.raw));
app.get("/hello/:name", async (c) => {
const name = c.req.param("name");
return c.text(`Hello ${name}!`);
});
On Cloudflare Workers/Deno/Bun just export the handler:
export default createFetchHandler(api.handler);
4. Start the runtime
Visit http://localhost:3000/tripsQuery to hit the hypequery route. Because basePath is empty, there’s no URL rewriting required.
5. Optional: keep api.run()
Even with HTTP routes mounted, you can still execute queries in-process:
import { api } from './analytics/queries';
await api.run('tripsQuery', {
request: { headers: { 'x-refresh': 'true' } },
});
Use this inside cron jobs, workers, or React Server Components where HTTP isn’t needed.
Notes
- Other runtimes: Remix loaders, Bun, Workers all accept the same fetch handler. Mount it under your desired prefix or export it directly.
- React hooks: Since
analytics/queries.tsexportsApiDefinition, you can generate hooks anywhere:createHooks<ApiDefinition>({ baseUrl: '/tripsQuery' }). - Multiple prefixes: If you keep
basePath: '/api/hypequery', mount once atapp.all('/api/hypequery/*', c => hypequery(c.req.raw))and the shared prefix takes care of routing.
That’s all! Your hypequery definitions now run inside any Fetch runtime alongside your existing routes.