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 devspins up the same HTTP server yourapi.handleruses, so once you register routes (step 1) every endpoint is reachable without any extra wiring. In production you can do the equivalent by callingawait api.start({ port }), or by embeddingapi.handlerinside 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
Embedded in Framework (Recommended for Web Apps)
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)
Standalone Server (Recommended for Microservices)
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
--portflag - 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
| Scenario | Recommendation | Why |
|---|---|---|
| Next.js/Remix app with dashboard | Embedded | Same port, no CORS, simpler deployment |
| React SPA + separate backend | Standalone | Decouple frontend and data API |
| Microservices architecture | Standalone | Independent scaling and deployment |
| Monolithic application | Embedded | Simpler infrastructure |
| Multiple consumers (web, mobile, external) | Standalone | Shared API service |
| Internal dashboard only | Embedded | Fewer moving parts |
Related: Next.js Integration | Authentication | React Hooks