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 { define, queries, query } = initServe({
  context: () => ({ db }),
});

export const api = define({
  queries: queries({
    revenue: query
      .describe('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];
      }),
  }),
});

// ✅ Register the route - required for HTTP access!
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, so once you register routes (step 1) every endpoint is reachable without any extra wiring. 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. In production you can call await api.start({ port: 3000 }) or reuse api.handler inside your framework (Next.js, Express, etc.) to serve the same routes—npx hypequery dev is simply the quickest way to preview that exact server locally.

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';

export const runtime = 'nodejs';

export const GET = api.handler;
export const POST = api.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/adapters/node';
import { api } from './analytics/queries';

const app = express();

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

app.listen(3000, () => {
  console.log('App + hypequery running on http://localhost:3000');
});

Example: Hono / Fetch runtimes

When you need an Edge-style handler (Cloudflare Workers, Deno, Bun), wrap api.handler with the fetch adapter:

// hono-app.ts
import { Hono } from 'hono';
import { createFetchHandler } from '@hypequery/serve/adapters/fetch';
import { api } from './analytics/queries';

const app = new Hono();
const hypequeryHandler = createFetchHandler(api.handler);

app.all('/api/hypequery/*', async (c) => {
  const url = new URL(c.req.url);
  url.pathname = url.pathname.replace(/^\/api\/hypequery/, '') || '/';
  return hypequeryHandler(new Request(url, c.req.raw));
});

export default app;

Port configuration:

  • Development: Same as your dev server (e.g., localhost:3000)
  • Production: Same as your deployed app (e.g., https://yourdomain.com)
  • Base URL: /api/hypequery (or whatever route you configure)

Run hypequery as a separate HTTP service using the CLI.

Start the server:

# Development
npx hypequery dev src/analytics/queries.ts

# Production
npx hypequery serve src/analytics/queries.ts --port 4000

Port configuration:

  • Default port: 4000
  • Custom port: Use --port flag
  • Base URL: http://localhost:4000

Frontend configuration:

// With Vite/React
import { createHooks } from '@hypequery/react';

export const { useQuery } = createHooks<DashboardApi>({
  baseUrl: 'http://localhost:4000',  // Point to standalone server
});

CORS handling:

When running on a different port, you may need CORS configuration. Use Vite’s proxy in development:

// vite.config.ts
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:4000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

Then use /api as your baseUrl:

export const { useQuery } = createHooks<DashboardApi>({
  baseUrl: '/api',  // Proxied to localhost:4000
});

Route Registration

Important: Queries must be explicitly registered to HTTP routes. Simply defining queries in define() is not enough.

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

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

export const api = define({
  queries: queries({
    revenue: query
      .output(z.object({ total: z.number() }))
      .query(async ({ ctx }) => {
        // ... query logic
      }),
  }),
});

// ❌ Won't work - query not registered to HTTP route
// The query exists but is not accessible via HTTP

// ✅ Correct - explicitly register route
api.route('/revenue', api.queries.revenue);

HTTP Method Configuration

Routes default to GET method. Specify POST or other methods in the route options:

api
  .route('/hello', api.queries.hello)                          // GET by default
  .route('/stats', api.queries.stats)                          // GET by default
  .route('/create-user', api.queries.createUser, { method: 'POST' })  // Explicit POST
  .route('/update-user', api.queries.updateUser, { method: 'PUT' })   // Explicit PUT
  .route('/delete-user', api.queries.deleteUser, { method: 'DELETE' }); // Explicit DELETE

When to use GET vs POST:

  • GET: No input, or simple query parameters (filters, pagination)
  • POST: Complex inputs, mutations, or REST conventions

Client auto-configuration:

When using @hypequery/react, HTTP methods are automatically extracted from your route configuration:

// queries.ts - Server-side route definition
api
  .route('/hello', api.queries.hello)                    // GET
  .route('/revenue', api.queries.revenue, { method: 'POST' });  // POST

// client.ts - Client automatically uses correct methods
import { createHooks } from '@hypequery/react';
import type { InferApiType } from '@hypequery/serve';
import { api } from './queries';

type MyApi = InferApiType<typeof api>;

export const { useQuery } = createHooks<MyApi>({
  baseUrl: '/api/hypequery',
  api,  // Auto-extracts: hello → GET, revenue → POST
});

No manual method configuration needed on the client!

OpenAPI Documentation

hypequery automatically generates OpenAPI 3.0 specifications from your query definitions.

Accessing OpenAPI Spec

Embedded mode:

# Your OpenAPI spec is available at:
http://localhost:3000/api/hypequery/openapi.json

Standalone mode:

# Start dev server
npx hypequery dev src/analytics/queries.ts

# OpenAPI spec available at:
http://localhost:4000/openapi.json

# Interactive docs at:
http://localhost:4000/docs

OpenAPI Generation

The spec is automatically generated from your Zod schemas:

const api = define({
  queries: queries({
    getRevenue: query
      .describe('Get revenue metrics for a date range')  // → OpenAPI description
      .input(z.object({
        startDate: z.string().describe('ISO 8601 start date'),  // → Parameter docs
        endDate: z.string().describe('ISO 8601 end date'),
      }))
      .output(z.object({
        total: z.number().describe('Total revenue'),  // → Response schema
        average: z.number().describe('Average per transaction'),
      }))
      .query(async ({ ctx, input }) => {
        // ... implementation
      }),
  }),
});

This generates:

{
  "openapi": "3.0.0",
  "paths": {
    "/getRevenue": {
      "post": {
        "summary": "Get revenue metrics for a date range",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "startDate": { "type": "string", "description": "ISO 8601 start date" },
                  "endDate": { "type": "string", "description": "ISO 8601 end date" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": { "type": "number", "description": "Total revenue" },
                    "average": { "type": "number", "description": "Average per transaction" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Client Hand-off

The OpenAPI spec enables easy integration with other teams and tools.

Code Generation

Use the OpenAPI spec to generate type-safe clients:

# Generate TypeScript client
npx @openapitools/openapi-generator-cli generate \
  -i http://localhost:4000/openapi.json \
  -g typescript-fetch \
  -o ./generated/api-client

# Generate Python client
npx @openapitools/openapi-generator-cli generate \
  -i http://localhost:4000/openapi.json \
  -g python \
  -o ./generated/python-client

API Testing Tools

Import the OpenAPI spec into:

  • Postman: File → Import → Paste URL to openapi.json
  • Insomnia: Import → From URL
  • Swagger UI: Point to your OpenAPI endpoint
  • Bruno: Import Collection → OpenAPI

Documentation Sites

Generate API documentation:

# Redoc
npx @redocly/cli build-docs http://localhost:4000/openapi.json

# Swagger UI
docker run -p 8080:8080 -e SWAGGER_JSON=/openapi.json \
  -v $(pwd)/openapi.json:/openapi.json \
  swaggerapi/swagger-ui

Development Workflow

Testing Queries

With CLI (standalone mode):

# Start dev server with auto-reload
npx hypequery dev src/analytics/queries.ts

# Open interactive docs
open http://localhost:4000/docs

# Test with curl
curl -X POST http://localhost:4000/revenue \
  -H "Content-Type: application/json" \
  -d '{"startDate": "2024-01-01", "endDate": "2024-12-31"}'

With framework adapter:

# Start your framework's dev server
npm run dev

# Routes available at framework's port
curl -X POST http://localhost:3000/api/hypequery/revenue \
  -H "Content-Type: application/json" \
  -d '{"startDate": "2024-01-01", "endDate": "2024-12-31"}'

Hot Reload

Standalone CLI automatically reloads when you change query files:

npx hypequery dev src/analytics/queries.ts
# Edit queries.ts → server automatically reloads

Framework mode uses your framework’s hot reload (e.g., Next.js Fast Refresh).

Production Deployment

Framework Deployment

Deploy as part of your application:

# Next.js example
npm run build
npm start

# Routes available at: https://yourdomain.com/api/hypequery/*

Standalone Deployment

Run as a separate service:

# Using Node.js
npx hypequery serve src/analytics/queries.ts --port 4000

# Using Docker
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["npx", "hypequery", "serve", "src/analytics/queries.ts", "--port", "4000"]
EXPOSE 4000

# Using PM2
pm2 start "npx hypequery serve src/analytics/queries.ts --port 4000" --name hypequery-api

Environment Variables

Configure production settings:

# Database connection
DATABASE_URL=clickhouse://prod-server:9000/analytics

# Server configuration
PORT=4000
NODE_ENV=production

# Start server
npx hypequery serve src/analytics/queries.ts --port $PORT

Choosing a Deployment Model

ScenarioRecommendationWhy
Next.js/Remix app with dashboardEmbeddedSame port, no CORS, simpler deployment
React SPA + separate backendStandaloneDecouple frontend and data API
Microservices architectureStandaloneIndependent scaling and deployment
Monolithic applicationEmbeddedSimpler infrastructure
Multiple consumers (web, mobile, external)StandaloneShared API service
Internal dashboard onlyEmbeddedFewer moving parts

Related: Next.js Integration | Authentication | React Hooks