Deploy to Cloudflare Workers
Stoma has first-class Cloudflare Workers support. Deploy your gateway to the edge with full access to Service Bindings, KV, Durable Objects, and the Cache API.
Prerequisites
Section titled “Prerequisites”npm install -g wranglerwrangler loginQuick Deploy
Section titled “Quick Deploy”-
Install dependencies
Terminal window npm install hono @homegrower-club/stoma -
Create your gateway
For a minimal example, see the Basic Gateway partial. Here’s a typical production setup with the Cloudflare adapter:
src/index.ts import {createGateway,cors,requestLog,rateLimit,} from "@homegrower-club/stoma";import { cloudflareAdapter } from "@homegrower-club/stoma/adapters/cloudflare";const gateway = createGateway({name: "my-gateway",basePath: "/api",policies: [requestLog(), cors()],routes: [{path: "/users/*",pipeline: {policies: [rateLimit({ max: 100, window: 60 })],upstream: {type: "url",target: "https://api.example.com",rewritePath: (path) => path.replace("/api", ""),},},},],},// Production adapter with KV rate limiting and Cache API supportcloudflareAdapter({ env }));export default gateway.app; -
Configure wrangler
wrangler.toml {"name": "my-gateway","compatibility_date": "2025-01-01","compatibility_flags": ["nodejs_compat"],"main": "src/index.ts"} -
Deploy
Terminal window wrangler deploy
Wrangler Configuration
Section titled “Wrangler Configuration”Basic Configuration
Section titled “Basic Configuration”{ "name": "my-gateway", "compatibility_date": "2025-01-01", "compatibility_flags": ["nodejs_compat"], "main": "src/index.ts", "observability": { "enabled": true }}| Option | Description |
|---|---|
name | Worker name (used in URL: <name>.workers.dev) |
compatibility_date | Use a recent date for latest features |
compatibility_flags | nodejs_compat enables Node.js compatibility |
main | Entry point for your Worker |
Environment Configs
Section titled “Environment Configs”Configure multiple environments for staging and production:
{ "name": "my-gateway", "main": "src/index.ts", "compatibility_date": "2025-01-01", "compatibility_flags": ["nodejs_compat"], "env": { "staging": { "routes": [ { "pattern": "staging.myapp.com/*", "zone_name": "myapp.com" } ] }, "production": { "routes": [ { "pattern": "api.myapp.com/*", "zone_name": "myapp.com" } ] } }}Deploy to a specific environment:
wrangler deploy --env stagingwrangler deploy --env productionSecrets Management
Section titled “Secrets Management”Using wrangler secret
Section titled “Using wrangler secret”Store sensitive values securely:
# Interactive - will prompt for valuewrangler secret put JWT_SECRET
# Or pipe from stdinecho "my-secret-value" | wrangler secret put API_KEYIn Your Code
Section titled “In Your Code”Access secrets via the env parameter:
interface Env { JWT_SECRET: string; API_KEY: string; DATABASE_URL: string;}
export default { fetch(request: Request, env: Env, ctx: ExecutionContext) { const gateway = createGateway({ // Use env.JWT_SECRET here }); return gateway.app.fetch(request, env, ctx); },};Secret Types
Section titled “Secret Types”| Secret Type | Use Case |
|---|---|
wrangler secret put | Runtime secrets (API keys, tokens) |
wrangler kv:namespace create | KV namespace binding |
wrangler d1 create | D1 database binding |
wrangler r2 bucket create | R2 storage binding |
Cloudflare-Specific Adapter
Section titled “Cloudflare-Specific Adapter”Use the Cloudflare adapter for Service Bindings, KV rate limiting, and Durable Objects:
import { createGateway, cors, rateLimit, cache, circuitBreaker,} from "@homegrower-club/stoma";import { cloudflareAdapter } from "@homegrower-club/stoma/adapters/cloudflare";
interface Env { JWT_SECRET: string; RATE_LIMIT_KV: KVNamespace; CACHE_KV: KVNamespace; USERS_SERVICE: Fetcher;}
export default { fetch(request: Request, env: Env, ctx: ExecutionContext) { const adapter = cloudflareAdapter({ rateLimitKv: env.RATE_LIMIT_KV, cacheKv: env.CACHE_KV, executionCtx: ctx, env, });
const gateway = createGateway({ name: "my-gateway", basePath: "/api", adapter, policies: [cors()], routes: [ { path: "/users/*", pipeline: { policies: [ rateLimit({ max: 100, windowSeconds: 60, store: adapter.rateLimitStore, }), ], upstream: { type: "service-binding", service: "USERS_SERVICE", }, }, }, { path: "/products/*", pipeline: { policies: [ cache({ ttlSeconds: 300, store: adapter.cacheStore, }), ], upstream: { type: "url", target: "https://products.internal", }, }, }, ], });
return gateway.app.fetch(request, env, ctx); },};GitHub Actions CI/CD
Section titled “GitHub Actions CI/CD”Automate deployments with GitHub Actions:
name: Deploy to Cloudflare Workers
on: push: branches: [main] pull_request: branches: [main]
jobs: deploy: runs-on: ubuntu-latest permissions: contents: read deployments: write
steps: - uses: actions/checkout@v4
- uses: actions/setup-node@v4 with: node-version: "20" cache: "npm"
- name: Install dependencies run: npm ci
- name: Run tests run: npm test
- name: Deploy to Cloudflare Workers uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: deploy --env production
- name: Deploy to staging if: github.event_name == 'pull_request' uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: deploy --env stagingRequired Secrets
Section titled “Required Secrets”Add these secrets to your GitHub repository:
| Secret | How to Get |
|---|---|
CLOUDFLARE_API_TOKEN | Cloudflare Dashboard → Profile → API Tokens → Create Token (use “Edit Cloudflare Workers” template) |
CLOUDFLARE_ACCOUNT_ID | Cloudflare Dashboard → Overview → Account ID |
Custom Domains
Section titled “Custom Domains”Workers
Section titled “Workers”# Add a custom domainwrangler routes update --pattern "api.example.com" --zone-name "example.com"Or in wrangler.toml:
{ "routes": [ { "pattern": "api.example.com/*", "zone_name": "example.com" } ]}For complex frontends with a gateway, consider Cloudflare Pages:
# Create a Pages projectwrangler pages project create my-gatewaywrangler pages deploy dist/ --project-name my-gatewayTroubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”“Module not found” errors
Ensure nodejs_compat is in your compatibility_flags:
{ "compatibility_flags": ["nodejs_compat"]}KV namespace not found
Bind the KV namespace in wrangler.toml:
{ "kv_namespaces": [ { "binding": "RATE_LIMIT_KV", "id": "your-namespace-id" } ]}Service Binding not working
Ensure the target Worker is deployed and accessible:
{ "services": [ { "binding": "USERS_SERVICE", "service": "users-worker" } ]}