Quick Start
In this guide, we’ll build a production-grade gateway with authentication, rate limiting, and a health check.
-
Initialize your Gateway Create a new file (e.g.,
src/index.ts) and usecreateGatewayto define your infrastructure.// Import the core function and policies you need// - createGateway: Compiles your config into a gateway application// - jwtAuth: Validates JWT tokens on incoming requests// - rateLimit: Limits requests per IP/user// - requestLog: Logs each request (timing, status, etc.)// - health: Adds a /health endpoint that probes your upstreamsimport { createGateway, jwtAuth, rateLimit, requestLog, health } from "@homegrower-club/stoma";// createGateway() validates your config at construction time// If anything is misconfigured, you get a clear error immediatelyconst gateway = createGateway({// name: A friendly identifier for logs, admin UI, and metrics// Shows up as "my-gateway" in your logs - helps identify which gatewayname: "my-gateway",// basePath: Prefix added to all route paths// Request to /v1/users/123 will match route path /users/123basePath: "/v1",// policies: Global policies that run on EVERY request// These execute before route-specific policies// - requestLog(): Logs request/response details for observabilitypolicies: [requestLog()],// routes: Array of route definitions, processed top-to-bottom// First matching route wins - use this for specific overridesroutes: [// health() is special - returns a RouteConfig, not a Policy// Adds GET /health that returns 200 if all upstreams are healthyhealth(),// Define a route with a path pattern{// path: URL pattern with wildcard support// * matches one segment: /users/* matches /users/123 but not /users/123/posts// ** matches multiple: /api/** matches /api/a/b/cpath: "/users/*",// pipeline: The middleware chain + upstream for this routepipeline: {// policies: Route-specific middleware// These run after global policies, in the order specifiedpolicies: [// jwtAuth: Validates Bearer token from Authorization header// Throws 401 if missing/invalid, otherwise adds user info to context// "env:JWT_SECRET" reads from environment variable at runtimejwtAuth({ secret: "env:JWT_SECRET" }),// rateLimit: Limits requests per window// max: 100 requests per windowSeconds (here: 60 seconds)// Uses in-memory store by default - swap for Redis/KV in productionrateLimit({ max: 100, windowSeconds: 60 }),],// upstream: Where to send the request after policies pass// Three types available:// - url: Proxy to any HTTP endpoint (most common)// - service-binding: Call another Cloudflare Worker directly// - handler: Return a response inline (for mocks/static responses)upstream: {// type: Which upstream configuration to usetype: "url",// target: The destination URL to proxy to// The request path (minus basePath) is appended heretarget: "https://users-api.internal.example.com",// Optional: rewritePath transforms the path before sending// If omitted, the matched route path is used as-is// Example: With basePath "/v1", request /v1/users/123 -> /users/123},},},],});// Export the gateway app - this is a standard fetch handler// Your runtime (Cloudflare, Node, Bun) will call gateway.app.fetchexport default gateway.app; -
Understand the Core Concepts
createGateway: The main entry point. It compiles your config into a gateway application.basePath: Prefixes all routes (e.g.,/v1/users/*).- Global Policies:
policies: [requestLog()]runs on every single request. - Pipeline: A chain of policies leading to an
upstream(where the request ends up).
-
Choose your Upstream Stoma supports three types of upstreams:
url: Proxy to any HTTP endpoint (Universal).service-binding: Zero-latency Worker-to-Worker routing (Cloudflare only).handler: Inline response logic (Universal).
-
Run and Deploy The
gateway.appexport is a standard Hono application with afetchhandler, so it runs on any JavaScript runtime.export default gateway.app;Run with
npx wrangler dev. See the Local Development guide for more options.import { serve } from "@hono/node-server";serve({ fetch: gateway.app.fetch, port: 3000 });// Bunexport default gateway.app;// DenoDeno.serve(gateway.app.fetch);