Local Development
This guide covers how to run your Stoma gateway locally during development. Choose the approach that matches your target runtime.
Option 1: Node.js with tsx
Section titled “Option 1: Node.js with tsx”The fastest way to iterate on your gateway is using Node.js with tsx (TypeScript execute). This works for any gateway that doesn’t rely on Cloudflare-specific bindings.
Prerequisites
Section titled “Prerequisites”npm install -D tsxCreate a runner script
Section titled “Create a runner script”Create a simple entry point that starts a local server:
import { serve } from "@hono/node-server";import { createGateway, rateLimit, health } from "@homegrower-club/stoma";import { memoryAdapter } from "@homegrower-club/stoma/adapters";
const adapter = memoryAdapter();
const gateway = createGateway({ name: "dev-gateway", basePath: "/api", routes: [ health({ path: "/health" }), { path: "/users/*", pipeline: { policies: [ rateLimit({ max: 100, windowSeconds: 60, store: adapter.rateLimitStore }), ], upstream: { type: "url", target: "https://jsonplaceholder.typicode.com", }, }, }, ],});
const port = Number(process.env.PORT) || 3000;
console.log(`🚀 Gateway running at http://localhost:${port}`);console.log(` Health: http://localhost:${port}/api/health`);
serve({ fetch: gateway.app.fetch, port,});Run with tsx
Section titled “Run with tsx”npx tsx runner.tsWhat’s supported
Section titled “What’s supported”- All URL-based upstreams
- Handler upstreams
- In-memory stores (rate limiting, caching, circuit breaker)
- All policies that don’t require Cloudflare-specific bindings
What’s NOT supported
Section titled “What’s NOT supported”- Service Bindings (Cloudflare-only)
- Cloudflare KV, Durable Objects, or other Workers bindings
- Browser Rendering bindings
Option 2: Cloudflare Workers with wrangler
Section titled “Option 2: Cloudflare Workers with wrangler”If your gateway uses Cloudflare-specific features, run it with wrangler dev.
Prerequisites
Section titled “Prerequisites”npm install -D wranglerCreate your gateway
Section titled “Create your gateway”import { createGateway, rateLimit, health, cors } from "@homegrower-club/stoma";
const gateway = createGateway({ name: "my-gateway", basePath: "/api", policies: [cors()], routes: [ health({ path: "/health" }), { path: "/users/*", pipeline: { policies: [ rateLimit({ max: 100, windowSeconds: 60 }), ], upstream: { type: "url", target: "https://jsonplaceholder.typicode.com", }, }, }, ],});
export default gateway.app;Configure wrangler
Section titled “Configure wrangler”Create a wrangler.jsonc in your project root:
{ "name": "my-gateway", "compatibility_date": "2025-01-01", "main": "src/index.ts"}Run locally
Section titled “Run locally”npx wrangler devThis starts a local dev server at http://localhost:8787. Any changes to your code trigger an automatic rebuild.
Using Service Bindings locally
Section titled “Using Service Bindings locally”If your gateway calls other Workers via Service Bindings, you can mock them in wrangler.jsonc:
{ "name": "my-gateway", "compatibility_date": "2025-01-01", "main": "src/index.ts", "services": [ { "binding": "AUTH_SERVICE", "service": "auth-worker" } ]}Using the demo app
Section titled “Using the demo app”The Stoma repository includes a demo gateway showcasing multiple policies. You can run it locally to explore features:
cd examples/cloudflare-workernpm installnpm run devThis runs the demo with wrangler dev, giving you a live gateway with:
- Rate limiting
- Circuit breaker
- Caching
- Request/response transforms
- Health checks
- Markdown rendering (requires Browser Rendering binding)
Environment variables
Section titled “Environment variables”Both Node.js and wrangler approaches support environment variables:
Create a .env file and use dotenv:
npm install dotenvimport "dotenv/config";// Your gateway code uses process.env.JWT_SECRETUse wrangler.jsonc secrets or .dev.vars for local development:
# .dev.vars (local only, not committed)JWT_SECRET=dev-secretSwitching between local modes
Section titled “Switching between local modes”For projects that target both Node.js and Cloudflare, you can conditionally use different adapters:
import { createGateway, rateLimit, health, cors } from "@homegrower-club/stoma";import { memoryAdapter } from "@homegrower-club/stoma/adapters";
const isCloudflare = process.env.WORKER_RUNTIME === "cloudflare";
const gateway = createGateway({ name: "my-gateway", adapter: isCloudflare ? undefined // Use default (Cloudflare) adapter : memoryAdapter(), // Use in-memory for Node.js dev // ... rest of config});Next Steps
Section titled “Next Steps”- Deploy to Cloudflare - Push your gateway to production
- Testing Guide - Write tests for your policies
- Recipes - Copy production-ready patterns