HTTP Routes Architecture
TinyKit Pro uses Hono with OpenAPI for HTTP endpoints, providing automatic API documentation, type-safe routing, and a modular domain-based organization patt...
TinyKit Pro uses Hono with OpenAPI for HTTP endpoints, providing automatic API documentation, type-safe routing, and a modular domain-based organization pattern.
Architecture Overview
HTTP routes are built with:
- Hono: Fast, lightweight web framework
- @hono/zod-openapi: Automatic OpenAPI spec generation
- Modular Organization: Domain-specific route modules
- Interactive Docs: Scalar UI at
/scalar
For detailed Hono + OpenAPI documentation, see Hono + OpenAPI Integration.
Example File Structure
convex/
├── http.ts # Main router registration
├── billing/
│ └── api.ts # Billing/Stripe routes
├── emails/
│ └── api.ts # Email/Resend routes
└── siteSettings/
└── api.ts # Site settings routesMain Router (http.ts)
The main HTTP router uses Hono with OpenAPI and wraps it with HttpRouterWithHono for Convex compatibility:
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { HttpRouterWithHono } from "convex-helpers/server/hono";
import { cors } from "hono/cors";
import { Scalar } from "@scalar/hono-api-reference";
import { ActionCtx } from "./_generated/server";
import { auth } from "./auth";
import { siteSettingsRoutes } from "./siteSettings/api";
import { emailRoutes } from "./emails/api";
import { billingRoutes } from "./billing/api";
type ConvexEnv = { Bindings: ActionCtx };
const app = new OpenAPIHono<ConvexEnv>();
// Enable CORS globally
app.use("/*", cors());
// Register domain-specific routes
siteSettingsRoutes(app);
emailRoutes(app);
billingRoutes(app);
// API documentation endpoints
app.get("/scalar", Scalar({ url: "/openapi" }));
app.doc("/openapi", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "TinyKit Pro API",
description: "REST API for TinyKit Pro SaaS boilerplate",
},
});
// Wrap with HttpRouterWithHono for Convex
const http = new HttpRouterWithHono(app);
// Register Convex Auth routes
auth.addHttpRoutes(http);
export default http;Route Modules
Module Pattern
Each route module follows the Hono + OpenAPI pattern:
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { ActionCtx } from "../_generated/server";
type ConvexEnv = { Bindings: ActionCtx };
// Define Zod schemas
const ResponseSchema = z
.object({
message: z.string(),
})
.openapi("Response");
// Define route configuration
const myRoute = createRoute({
method: "get",
path: "/endpoint-path",
responses: {
200: {
content: {
"application/json": {
schema: ResponseSchema,
},
},
description: "Success response",
},
},
tags: ["Domain"],
});
/**
* Register [domain] HTTP routes
* @param app - OpenAPIHono app instance
*/
export function domainRoutes(app: OpenAPIHono<ConvexEnv>) {
app.openapi(myRoute, async (c) => {
// Access Convex context
const result = await c.env.runQuery(api.example.getData, {});
return c.json(result as never);
});
}Billing Routes (convex/billing/api.ts)
Handles Stripe webhook events:
export function billingRoutes(http: HttpRouter) {
/**
* Stripe webhook endpoint for subscription and payment events
* Validates signature and delegates to handleStripeEvent
*/
http.route({
path: "/stripe-webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
const signature = request.headers.get("stripe-signature")!;
const payload = await request.text();
const result = await ctx.runAction(
internal.billing.internal.webhooks.handleStripeEvent,
{ signature, payload },
);
return result.success
? new Response(null, { status: 200 })
: new Response("Webhook Error", { status: 400 });
}),
});
}Email Routes (convex/emails/api.ts)
Handles Resend email webhooks:
export function emailRoutes(http: HttpRouter) {
/**
* Resend webhook endpoint for email event tracking
* Handles delivery, bounce, and complaint notifications
*/
http.route({
path: "/resend-webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
logger.info("🔔 Received Resend webhook request");
const result = await resend.handleResendEventWebhook(ctx, request);
logger.info("✅ Resend webhook processed successfully");
return result;
}),
});
}Site Settings Routes (convex/siteSettings/api.ts)
Public endpoints for OG image generation:
export function siteSettingsRoutes(http: HttpRouter) {
/**
* Public endpoint for OG image generation
* Returns site settings for dynamic Open Graph images
*/
http.route({
path: "/og-settings",
method: "GET",
handler: httpAction(async (ctx) => {
const settings = await ctx.runQuery(
api.siteSettings.public.queries.getPublicSettings,
);
return new Response(JSON.stringify(settings.ogImageSettings), {
status: 200,
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*",
},
});
}),
});
/**
* Custom OG image URL endpoint
* Returns storage URL if custom image exists
*/
http.route({
path: "/og-image-url",
method: "GET",
handler: httpAction(async (ctx) => {
const imageUrl = await ctx.runQuery(
api.siteSettings.public.queries.getOGImageUrl,
);
return new Response(JSON.stringify({ imageUrl }), {
status: 200,
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*",
},
});
}),
});
}Available Endpoints
Public Endpoints
| Path | Method | Purpose | Module |
|---|---|---|---|
/health | GET | Health check for monitoring | Main router |
/og-settings | GET | OG image generation settings | siteSettings |
/og-image-url | GET | Custom OG image URL | siteSettings |
Webhook Endpoints
| Path | Method | Purpose | Module |
|---|---|---|---|
/stripe-webhook | POST | Stripe billing events | billing |
/resend-webhook | POST | Email delivery events | emails |
Authentication Endpoints
Authentication endpoints are automatically registered by Convex Auth:
/api/auth/*- OAuth flows, sign-in, sign-up- See Convex Auth documentation for details
Adding New Routes
1. Create Route Module
Create a new file in the appropriate domain folder:
// convex/yourDomain/api.ts
import { HttpRouter } from "convex/server";
import { httpAction } from "../_generated/server";
export function yourDomainRoutes(http: HttpRouter) {
http.route({
path: "/your-endpoint",
method: "GET",
handler: httpAction(async (ctx) => {
// Implementation
return new Response("OK", { status: 200 });
}),
});
}2. Register in Main Router
Import and register in convex/http.ts:
import { yourDomainRoutes } from "./yourDomain/api";
// ... other imports
const http = httpRouter();
auth.addHttpRoutes(http);
// Register your routes
yourDomainRoutes(http);
// ... other route registrations
export default http;3. Best Practices
- Group by domain: Routes should be grouped by their business domain
- Clear naming: Use descriptive function names like
billingRoutes,emailRoutes - Documentation: Add JSDoc comments explaining what each endpoint does
- Error handling: Always include try/catch and appropriate error responses
- Logging: Use the logger for important events and errors
- Type safety: Import types from
_generated/apiand_generated/server
Response Patterns
Success Response
return new Response(JSON.stringify(data), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});Error Response
try {
// ... operation
} catch (error) {
logger.error("Operation failed:", error);
return new Response(JSON.stringify({ error: "Operation failed" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}Cached Response (Public Endpoints)
return new Response(JSON.stringify(data), {
status: 200,
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=3600", // 1 hour
"Access-Control-Allow-Origin": "*",
},
});Benefits of Modular Routes
✅ Better Organization: Routes grouped by domain instead of one large file ✅ Scalability: Easy to add new endpoints without cluttering main router ✅ Maintainability: Changes to domain routes don't affect other domains ✅ Clear Ownership: Each module owns its HTTP endpoints ✅ Testability: Easier to test individual route modules ✅ Discoverability: Clear file structure makes endpoints easy to find
Migration from Monolithic Router
If you have routes defined in a single http.ts file:
- Create module file:
convex/domain/api.ts - Move routes: Copy route definitions to the module
- Export function: Wrap routes in
export function domainRoutes(http: HttpRouter) - Update imports: Import dependencies relative to module location
- Register: Import and call in main
http.ts - Test: Verify endpoints still work correctly
Related Documentation
- API Reference - Convex function patterns
- Architecture - System design overview
- Development Guide - Development workflows
Convex Best Practices & AI Development Ruleset
This document serves as a comprehensive ruleset for AI assistants and developers working with Convex in TinyKit Pro. It combines the philosophy of Convex dev...
Production Checklist
Complete guide for deploying TinyKit Pro to production. Use this checklist to ensure a smooth go-live.