API Gateway
Declarative HTTP routing with type-safe contracts, authentication, and rate limiting.
Prerequisites: YAML Manifests, Core Concepts
Overview
Section titled “Overview”The z0 API Gateway layer enables you to define HTTP routes declaratively in your YAML manifest and have the SDK handle routing, authentication, validation, and response shaping automatically.
API Gateway is ideal for:
- Contract-first development with type-safe oRPC contracts and OpenAPI generation
- Declarative routing where routes are defined alongside entities and configs
- Built-in middleware for auth, rate limiting, and validation
- Consistent API responses with standard envelopes and RFC 7807 errors
The Gateway layer supports two approaches:
- GatewayWorker base class - Extend
GatewayWorkerto inherit routing and middleware - Code generation - Generate complete Worker code from manifest using
z0 generate gateway
Both approaches use the same manifest schema and provide identical functionality.
name: analytics-apiversion: 1.0.0
routes: - path: /v1/track method: POST entity: pageview action: emit factType: viewed auth: api_key rateLimit: { rpm: 1000, scope: tenant }
- path: /v1/sessions/:id method: GET entity: session action: get auth: api_keyRoutes Schema
Section titled “Routes Schema”Routes are defined in the routes section of your YAML manifest. Each route specifies the HTTP method, path, entity type, and action to perform.
Route Definition
Section titled “Route Definition”routes: - path: /v1/entities/:id # URL path (supports :param syntax) method: GET # HTTP method: GET, POST, PUT, PATCH, DELETE entity: account # Target entity type action: get # Action: emit, get, create, list, query factType: viewed # (Optional) Fact type for emit actions auth: api_key # (Optional) Auth mode: api_key, public rateLimit: # (Optional) Rate limiting config rpm: 1000 # Requests per minute scope: tenant # Scope: tenant, globalAvailable Actions
Section titled “Available Actions”| Action | Method | Purpose | Input | Output |
|---|---|---|---|---|
emit | POST | Append a fact to an entity | { entityId, factType, data } | { factId, seq, timestamp } |
get | GET | Retrieve an entity by ID | { entityId } | Entity object |
create | POST | Create a new entity | { type, data } | { id, entity } |
list | GET | List entities with pagination | { entityType, cursor?, limit? } | { items, cursor?, hasMore } |
query | POST | Execute a projection query | { projection, filters? } | { results, count } |
Path Parameters
Section titled “Path Parameters”Routes support path parameters using :paramName syntax:
routes: - path: /v1/accounts/:accountId method: GET entity: account action: get
- path: /v1/accounts/:accountId/transactions/:txId method: GET entity: transaction action: getParameters are extracted from the URL and passed to the handler.
GatewayWorker Base Class
Section titled “GatewayWorker Base Class”The GatewayWorker class provides a base implementation for API Gateway Workers. It automatically registers routes from your manifest and provides extensibility hooks.
Basic Usage
Section titled “Basic Usage”import { GatewayWorker, type GatewayWorkerEnv } from '@z0-app/sdk';import { parseManifest } from '@z0-app/sdk';import manifestYaml from './manifest.yaml';
// Parse manifestconst { manifest } = parseManifest(manifestYaml);
// Extend GatewayWorkerexport class MyGateway extends GatewayWorker<GatewayWorkerEnv> { constructor(state: DurableObjectState, env: GatewayWorkerEnv) { super(state, env, manifest); }}
// Export as Durable Objectexport { MyGateway };Adding Custom Routes
Section titled “Adding Custom Routes”Add custom routes in your constructor after calling super():
export class MyGateway extends GatewayWorker<GatewayWorkerEnv> { constructor(state: DurableObjectState, env: GatewayWorkerEnv) { super(state, env, manifest);
// Add custom health check endpoint this.router.get('/health', () => { return Response.json({ status: 'ok', timestamp: Date.now() }); });
// Add custom analytics endpoint this.router.post('/v1/batch', async (req) => { const events = await req.json(); // Process batch events return Response.json({ processed: events.length }); }); }}Lifecycle Hooks
Section titled “Lifecycle Hooks”Override lifecycle hooks to add custom logic:
export class MyGateway extends GatewayWorker<GatewayWorkerEnv> { /** * Called before route handling * Use for: logging, custom auth, request transformation */ protected async beforeRoute(request: Request): Promise<void> { // Log incoming requests console.log(`[${new Date().toISOString()}] ${request.method} ${new URL(request.url).pathname}`);
// Add custom validation const contentType = request.headers.get('Content-Type'); if (request.method === 'POST' && !contentType?.includes('application/json')) { throw new Error('Content-Type must be application/json'); } }
/** * Called after route handling * Use for: response transformation, custom headers, logging */ protected async afterRoute(request: Request, response: Response): Promise<Response> { // Clone response to add headers const headers = new Headers(response.headers); headers.set('X-API-Version', '1.0.0'); headers.set('X-Request-ID', crypto.randomUUID());
return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); }}Middleware
Section titled “Middleware”The Gateway layer includes built-in middleware for common API concerns.
Authentication Middleware
Section titled “Authentication Middleware”Two authentication modes are supported:
api_key Mode
Section titled “api_key Mode”Requires a valid API key in the X-API-Key header:
routes: - path: /v1/track method: POST entity: pageview action: emit auth: api_key # Validates X-API-Key headerThe auth middleware uses the SDK’s authenticateRequest() function to validate keys. Keys must have the format {prefix}_{random} (e.g., z0_abc123).
Invalid requests return RFC 7807 errors:
{ "type": "https://docs.z0.app/errors/unauthorized", "title": "Unauthorized", "status": 401, "detail": "Invalid or missing API key", "instance": "/errors/550e8400-e29b-41d4-a716-446655440000"}public Mode
Section titled “public Mode”Bypasses authentication for public endpoints:
routes: - path: /health method: GET entity: system action: get auth: public # No authentication requiredRate Limiting Middleware
Section titled “Rate Limiting Middleware”Per-route rate limiting prevents abuse and ensures fair resource usage:
routes: - path: /v1/track method: POST entity: pageview action: emit rateLimit: rpm: 1000 # Requests per minute scope: tenant # Scope: tenant or globalScope options:
tenant- Limit applies per tenant (identified by API key)global- Limit applies across all requests to this route
Rate limit exceeded returns 429:
{ "type": "https://docs.z0.app/errors/rate-limit-exceeded", "title": "Too Many Requests", "status": 429, "detail": "Rate limit exceeded: 1000 requests per minute", "instance": "/errors/550e8400-e29b-41d4-a716-446655440000"}Response includes Retry-After header indicating seconds until limit resets.
Request Validation Middleware
Section titled “Request Validation Middleware”Request bodies are automatically validated against Zod schemas defined in the oRPC contracts:
// For emit action{ "entityId": "session_abc123", "factType": "viewed", "data": { "url": "https://example.com/page", "duration_ms": 5000 }}Invalid requests return 400 with field-level errors:
{ "type": "https://docs.z0.app/errors/validation-failed", "title": "Validation Failed", "status": 400, "detail": "Request validation failed", "errors": [ { "field": "entityId", "code": "required", "message": "entityId is required" } ], "instance": "/errors/550e8400-e29b-41d4-a716-446655440000"}Response Shaping
Section titled “Response Shaping”All responses follow a consistent envelope format.
Success Responses
Section titled “Success Responses”Success responses use the { data, meta } envelope:
{ "data": { "factId": "fact_abc123", "seq": 42, "timestamp": 1678901234567 }, "meta": { "request_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": 1678901234567 }}The meta object always includes:
request_id- Unique identifier for request tracingtimestamp- Unix timestamp (milliseconds) when response was generated
Error Responses (RFC 7807)
Section titled “Error Responses (RFC 7807)”Error responses follow RFC 7807 Problem Details format:
{ "type": "https://docs.z0.app/errors/not-found", "title": "Not Found", "status": 404, "detail": "Entity account_xyz not found", "instance": "/errors/550e8400-e29b-41d4-a716-446655440000", "trace_id": "550e8400-e29b-41d4-a716-446655440000"}RFC 7807 fields:
type- URI reference identifying the problem typetitle- Short, human-readable summarystatus- HTTP status codedetail- Explanation specific to this occurrenceinstance- URI reference identifying this occurrence (includes request ID)errors- (Optional) Array of field-level validation errorstrace_id- (Optional) Request ID for distributed tracing
Content-Type: application/problem+json
oRPC Contracts
Section titled “oRPC Contracts”The Gateway uses oRPC contracts to define type-safe action interfaces with automatic validation.
Contract Definitions
Section titled “Contract Definitions”import { gatewayContract } from '@z0-app/sdk';
// Contract includes all action typesexport const gatewayContract = oc.router({ emit: oc .input(z.object({ entityId: z.string(), factType: z.string(), data: z.record(z.unknown()), })) .output(z.object({ factId: z.string(), seq: z.number(), timestamp: z.number(), })),
get: oc .input(z.object({ entityId: z.string() })) .output(EntitySchema),
// ... other actions});Using Contracts in TypeScript
Section titled “Using Contracts in TypeScript”import type { EmitRequest, EmitResponse, GetRequest, GetResponse,} from '@z0-app/sdk';
// Type-safe requestconst request: EmitRequest = { entityId: 'session_abc123', factType: 'viewed', data: { url: 'https://example.com' }};
// Type-safe responseconst response: EmitResponse = await client.emit(request);console.log(response.factId); // TypeScript knows this existsAvailable Types
Section titled “Available Types”All contract types are exported from the SDK:
import type { // Action request types EmitRequest, GetRequest, CreateRequest, ListRequest, QueryRequest,
// Action response types EmitResponse, GetResponse, CreateResponse, ListResponse, QueryResponse,
// Error types ProblemDetails, FieldError,
// SDK primitive types Entity, Fact,} from '@z0-app/sdk';Code Generation
Section titled “Code Generation”Generate complete Worker code from your manifest using the z0 CLI.
Generate Gateway Worker
Section titled “Generate Gateway Worker”npx z0 generate gateway --manifest manifest.yaml --output src/generatedGenerated files:
src/generated/gateway.ts- Complete Worker implementation extending GatewayWorker
Generated code includes:
- All routes from manifest registered with router
- Auth middleware for routes with
authconfig - Rate limit middleware for routes with
rateLimitconfig - Request validation for all actions
- Response shaping with standard envelope
- RFC 7807 error handling
- Health check endpoint (
/health)
Using Generated Code
Section titled “Using Generated Code”export { GeneratedGateway as default } from './generated/gateway';Wrangler configuration:
name = "my-api-gateway"main = "src/index.ts"compatibility_date = "2024-01-01"
[[durable_objects.bindings]]name = "GATEWAY"class_name = "GeneratedGateway"script_name = "my-api-gateway"Generate OpenAPI Spec
Section titled “Generate OpenAPI Spec”Generate OpenAPI 3.0 specification from oRPC contracts:
npx z0 generate openapi --manifest manifest.yaml --output openapi.jsonGenerated spec includes:
- All routes from manifest as OpenAPI paths
- Zod schemas converted to JSON Schema
- Request/response schemas for each action
- Error response schemas (RFC 7807)
- Authentication requirements (API key)
Use the generated spec for:
- API documentation tools (Swagger UI, Redoc)
- Client SDK generation (OpenAPI Generator)
- Contract testing (Prism, Dredd)
- Import into API management platforms
Validate Manifest
Section titled “Validate Manifest”Validate your manifest before generation:
npx z0 check --manifest manifest.yamlReturns exit code 0 for valid manifests, non-zero with error details for invalid manifests.
Complete Example
Section titled “Complete Example”Here’s a complete example building an analytics API:
1. Define Manifest
Section titled “1. Define Manifest”name: analytics-apiversion: 1.0.0
entities: pageview: description: Single page view event fields: url: { type: string, indexed: true } duration_ms: { type: number } user_agent: { type: string } facts: - viewed - engaged
session: description: User session fields: started_at: { type: number } last_active_at: { type: number } page_count: { type: number } facts: - started - ended
routes: # Track pageview (append fact) - path: /v1/track method: POST entity: pageview action: emit factType: viewed auth: api_key rateLimit: { rpm: 1000, scope: tenant }
# Get session (retrieve entity) - path: /v1/sessions/:id method: GET entity: session action: get auth: api_key
# List pageviews (paginated) - path: /v1/pageviews method: GET entity: pageview action: list auth: api_key rateLimit: { rpm: 100, scope: tenant }
# Public health check - path: /health method: GET entity: system action: get auth: public2. Implement Gateway
Section titled “2. Implement Gateway”import { GatewayWorker, type GatewayWorkerEnv, parseManifest } from '@z0-app/sdk';import manifestYaml from './manifest.yaml';
const { manifest } = parseManifest(manifestYaml);
export class AnalyticsGateway extends GatewayWorker<GatewayWorkerEnv> { constructor(state: DurableObjectState, env: GatewayWorkerEnv) { super(state, env, manifest);
// Add custom analytics endpoint this.router.post('/v1/batch', async (req) => { const events = await req.json();
// Process batch of events const results = await Promise.all( events.map(event => this.processBatchEvent(event)) );
return Response.json({ data: { processed: results.length, success: results.filter(r => r.ok).length }, meta: { request_id: crypto.randomUUID(), timestamp: Date.now() } }); }); }
private async processBatchEvent(event: any): Promise<{ ok: boolean }> { // Batch processing logic return { ok: true }; }
protected async beforeRoute(request: Request): Promise<void> { // Log all requests const url = new URL(request.url); console.log(`[Gateway] ${request.method} ${url.pathname}`); }
protected async afterRoute(request: Request, response: Response): Promise<Response> { // Add CORS headers const headers = new Headers(response.headers); headers.set('Access-Control-Allow-Origin', '*'); headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); }}
export { AnalyticsGateway as default };3. Configure Wrangler
Section titled “3. Configure Wrangler”name = "analytics-api"main = "src/gateway.ts"compatibility_date = "2024-01-01"
[[durable_objects.bindings]]name = "ANALYTICS_GATEWAY"class_name = "AnalyticsGateway"script_name = "analytics-api"
[[d1_databases]]binding = "DB"database_name = "analytics"database_id = "..."4. Deploy
Section titled “4. Deploy”npx wrangler deploy5. Test
Section titled “5. Test”# Track a pageviewcurl -X POST https://analytics-api.your-workers.dev/v1/track \ -H "X-API-Key: YOUR_API_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "entityId": "pv_abc123", "factType": "viewed", "data": { "url": "https://example.com/page", "duration_ms": 5000, "user_agent": "Mozilla/5.0..." } }'
# Response:{ "data": { "factId": "fact_xyz789", "seq": 1, "timestamp": 1678901234567 }, "meta": { "request_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": 1678901234567 }}
# Get a sessioncurl https://analytics-api.your-workers.dev/v1/sessions/session_123 \ -H "X-API-Key: YOUR_API_KEY_HERE"
# Response:{ "data": { "id": "session_123", "type": "session", "version": 3, "data": { "started_at": 1678900000000, "last_active_at": 1678901234567, "page_count": 5 }, "created_at": 1678900000000, "updated_at": 1678901234567 }, "meta": { "request_id": "660e8400-e29b-41d4-a716-446655440001", "timestamp": 1678901234567 }}Summary
Section titled “Summary”| Component | Purpose |
|---|---|
| GatewayWorker | Base class for API Gateway Workers with automatic routing |
| Routes schema | Declarative route definitions in YAML manifest |
| Auth middleware | API key validation with public route support |
| Rate limit middleware | Per-route rate limiting (tenant/global scopes) |
| Validation middleware | Automatic request validation via Zod schemas |
| Response shaping | Standard envelope ({ data, meta }) for success responses |
| RFC 7807 errors | Problem Details format with request ID tracing |
| oRPC contracts | Type-safe action interfaces with automatic validation |
| Code generation | Generate complete Workers from manifests |
| OpenAPI generation | Auto-generate OpenAPI 3.0 specs from contracts |
Next Steps
Section titled “Next Steps”- YAML Manifests - Full manifest schema reference
- Building Your First Domain - Entity and fact patterns
- Authentication - API key generation and management
- Utilities - Error handling, webhooks, and helpers