> hypequery

HTTP + OpenAPI

Expose hypequery metrics through HTTP handlers and autogenerated docs

HTTP + OpenAPI Delivery

hypequery can expose your queries as HTTP endpoints with automatically generated OpenAPI documentation.

Quick Example

Here's a complete example of exposing a query via HTTP:

1. Define your query:

// src/analytics/queries.ts
import { initServe } from '@hypequery/serve';
import { z } from 'zod';
import { db } from './client';

const { query, serve } = initServe({
  context: () => ({ db }),
});

const revenue = query({
  description: 'Get total revenue',
  output: z.object({
    total: z.number(),
    count: z.number(),
  }),
  query: async ({ ctx }) => {
    const rows = await ctx.db
      .table('orders')
      .sum('amount', 'total')
      .count('order_id', 'count')
      .execute();

    return rows[0];
  },
});

export const api = serve({
  queries: { revenue },
});

// Optional: expose the query at a custom path/method.
// Without this, the query is already reachable at POST /queries/revenue.
api.route('/revenue', api.queries.revenue, { method: 'POST' });

2. Start the server:

npx hypequery dev src/analytics/queries.ts
# Server running at http://localhost:4000

hypequery dev spins up the same HTTP server your api.handler uses. Every registered query is reachable at POST /queries/<key> automatically; api.route(...) is only needed when you want a custom path or method (like the /revenue route above). In production you can do the equivalent by calling await api.start({ port }), or by embedding api.handler inside your own framework/server if you don't want to rely on the CLI entry point.

3. Call your API:

curl -X POST http://localhost:4000/revenue
# {"total": 125000, "count": 450}

That's it! Your query is now available as an HTTP endpoint with auto-generated OpenAPI docs at http://localhost:4000/docs.

Deployment Models

Integrate hypequery directly into your web framework. Routes run on the same port as your application.

Supported frameworks:

  • Next.js (Vercel adapter)
  • Express
  • Hono
  • Any framework with standard Request/Response handlers

Example: Next.js

// app/api/hypequery/[...hq]/route.ts
import { api } from '@/analytics/queries';
import { createFetchHandler } from '@hypequery/serve';

export const runtime = 'nodejs';

const handler = createFetchHandler(api.handler);

export const GET = handler;
export const POST = handler;
export const OPTIONS = handler;

Example: Express

Use the Node adapter to mount hypequery alongside your existing routes:

// server.ts
import express from 'express';
import { createNodeHandler } from '@hypequery/serve';
import { api } from './analytics/queries';

const app = express();

// Mount hypequery at /api/analytics
app.use('/api/analytics', createNodeHandler(api.handler));

app.listen(3000);

Standalone Server

Run a dedicated hypequery server on its own port:

// src/analytics/server.ts
import { api } from './queries';

await api.start({ port: 4000 });

api.start() starts the default Node server and returns a { stop() } handle for shutdown. If you need custom app routes, mount api.handler inside your own Express, Hono, or Next.js server instead of expecting an app object from start().

For local development, use the CLI:

npx hypequery dev src/analytics/queries.ts --port 4000

In production, start the server yourself with api.start({ port }) (above) or mount api.handler inside your own framework — the CLI's dev command is for local development, not production serving.

Edge/Fetch Runtimes

Deploy to edge platforms using the standard Fetch API:

// Cloudflare Worker
import { api } from './analytics/queries';
import { createFetchHandler } from '@hypequery/serve';

const handler = createFetchHandler(api.handler);

export default {
  fetch(request: Request) {
    return handler(request);
  },
};

// Vercel Edge Function
import { api } from './analytics/queries';
import { createFetchHandler } from '@hypequery/serve';

const fetchHandler = createFetchHandler(api.handler);

export default function handler(request: Request) {
  return fetchHandler(request);
}

OpenAPI Documentation

Auto-generated OpenAPI specs are available at /openapi.json:

curl http://localhost:4000/openapi.json

Customize the generated OpenAPI document through serve({ openapi: ... }):

const api = serve({
  queries: { revenue },
  openapi: {
    title: 'My Analytics API',
    version: '2.0.0',
    servers: [
      { url: 'https://api.example.com', description: 'Production' },
    ],
  },
});

Semantic dataset and metric endpoints

Alongside hand-written queries, serve({ ... }) accepts metrics and datasets built with @hypequery/datasets. These generate governed POST endpoints automatically — you do not call api.route(...) for them, and they appear in /openapi.json and /docs like any other endpoint.

// src/analytics/queries.ts
import { initServe } from '@hypequery/serve';
import { db } from './client';
import { Orders, revenue } from './datasets/orders';

const { serve } = initServe({
  context: () => ({ db }),
});

export const api = serve({
  // Required whenever metrics or datasets are registered.
  queryBuilder: db,
  metrics: { revenue },
  datasets: { orders: Orders },
});

This registers:

ConfigGenerated endpoint
metrics: { revenue }POST /metrics/revenue
datasets: { orders: Orders }POST /datasets/orders/query

Both validate the request body (dimensions, measures, filters, ordering, time grain) against the metric or dataset contract before executing. A requested limit above the endpoint's maxLimit is clamped to maxLimit rather than rejected. queryBuilder is required whenever metrics or datasets are present; it can also be supplied through context as context: () => ({ db }).

Customize the path prefixes with semanticPaths:

export const api = serve({
  queryBuilder: db,
  metrics: { revenue },
  datasets: { orders: Orders },
  semanticPaths: {
    metrics: '/api/metrics',
    datasets: '/api/data',
  },
});
// POST /api/metrics/revenue
// POST /api/data/orders/query

For per-entry options (auth, caching, limits) and request/response shapes, see Serve integration.

Pagination

Semantic endpoints support offset pagination. Send limit and offset in the request body, and the response reports the offset it served along with hasMore, so clients know whether to fetch the next page.

curl -X POST http://localhost:4000/datasets/orders/query \
  -H 'content-type: application/json' \
  -d '{ "dimensions": ["country"], "measures": ["revenue"], "limit": 50, "offset": 50 }'

In React, useInfiniteMetric and useInfiniteDataset consume hasMore/offset for you — see the React API reference.

Result metadata

Semantic endpoints return { data } by default. Opt into metadata (generated SQL, timing, row count, tenant, pagination) by sending includeMeta: true in the request body or the x-include-meta: true header, which switches the response to { data, meta }.

curl -X POST http://localhost:4000/datasets/orders/query \
  -H 'content-type: application/json' \
  -H 'x-include-meta: true' \
  -d '{ "dimensions": ["country"], "measures": ["revenue"], "limit": 10 }'

Documentation UI

Serve interactive API documentation from the built-in /docs route, or customize the generated page with serve({ docs: ... }):

const api = serve({
  queries: { revenue },
  docs: {
    title: 'My API Docs',
    subtitle: 'Analytics runtime',
    darkMode: true,
  },
});

The runtime will expose that UI at /docs automatically once routes are registered.

If you want to host the docs HTML yourself, use the exported helper:

import { buildDocsHtml } from '@hypequery/serve';

app.get('/docs', (req, res) => {
  res.send(buildDocsHtml('/openapi.json', {
    title: 'My API Docs',
    subtitle: 'Analytics runtime',
    darkMode: true,
  }));
});

Custom Routes

Register individual queries with custom paths and methods:

import { api } from './analytics/queries';

// Register with custom path
api.route('/analytics/revenue', api.queries.revenue, { method: 'POST' });

// Register as GET
api.route('/health', api.queries.healthCheck, { method: 'GET' });

// Use query metadata from definition
api.route('/weekly', api.queries.weeklyRevenue);

On this page