npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@homegrower-club/stoma

v0.1.0-rc.5

Published

Declarative API gateway as a TypeScript library. Runs on Cloudflare Workers, Node.js, Deno, Bun, and any JavaScript runtime.

Downloads

197

Readme

Stoma 🌱

Declarative API gateway as a TypeScript library. Runs on Cloudflare Workers, Node.js, Deno, Bun, and more.

TypeScript Hono License


Why

API gateways in the JavaScript ecosystem are stuck between two bad options:

  1. Infrastructure you deploy -- Kong, KrakenD, AWS API Gateway. Powerful, but they are separate services you provision, configure with YAML, and operate independently from your application code. You lose type safety, your gateway config drifts from your codebase, and you pay for another piece of infrastructure.

  2. Dead or abandoned libraries -- Express Gateway was the closest thing to a TS-native gateway. It is unmaintained, locked to Express 4, and has no story for modern runtimes.

There is a third option: a declarative gateway library that lives in your codebase and deploys with your standard workflow.

@homegrower-club/stoma fills this gap. It is a TypeScript library you import -- not a Go binary or YAML config you deploy. Define your routes, policies, and upstreams as typed objects. The library compiles them into an HTTP application you can export directly.

  • Configured with TypeScript, not YAML
  • Lives in your codebase -- deploy as part of your app or as a standalone service
  • Runs on Cloudflare Workers, Node.js, Deno, Bun, Fastly, Lambda@Edge, Vercel Edge Functions
  • Optionally leverages platform-specific features (Cloudflare Service Bindings, KV, Durable Objects) through a pluggable adapter system

Think KrakenD's declarative route-to-pipeline model, but as a TypeScript library.

Features

  • TypeScript-first with full type safety -- Every config object, policy, and upstream is typed. No YAML. No JSON Schema. Your editor catches mistakes before your users do.
  • Powered by Hono -- lightweight, zero dependencies, Web Standards API. One of the fastest routers in the JavaScript ecosystem.
  • Declarative route / pipeline / policy chain model -- Define routes, attach ordered policy chains, and point at an upstream. The gateway wires it all together.
  • Dozens of built-in policies -- Auth (JWT, API key, OAuth2, RBAC, HTTP signatures), traffic control (rate limiting, IP filtering, caching, threat protection), resilience (circuit breaker, retry, timeout), transforms (CORS, request/response rewriting, validation), and observability (structured logging, metrics, health checks).
  • Multi-runtime -- Core depends only on Hono and the Web Request/Response API. No Node.js built-ins, no platform lock-in.
  • Pluggable adapter system -- Runtime-specific capabilities (distributed stores, background tasks, service bindings) are injected through adapters, not hard-coded. Adapters ship for Cloudflare Workers, Node.js, Deno, Bun, and in-memory development.
  • Pluggable policy system -- Policies are middleware functions with metadata. Write a custom policy in a few lines, or use definePolicy() from the SDK for a structured approach.

Installation

npm install @homegrower-club/stoma hono

Quick Start

Basic gateway (runs on any runtime)

import { createGateway, jwtAuth, rateLimit, requestLog, cors } from "@homegrower-club/stoma";

const gateway = createGateway({
  name: "my-api-gateway",
  basePath: "/api",
  policies: [requestLog(), cors()],
  routes: [
    {
      path: "/users/*",
      methods: ["GET", "POST", "PUT", "DELETE"],
      pipeline: {
        policies: [
          jwtAuth({ secret: "your-secret" }),
          rateLimit({ max: 100, windowSeconds: 60 }),
        ],
        upstream: {
          type: "url",
          target: "https://users-api.internal.example.com",
          rewritePath: (path) => path.replace("/api/users", ""),
        },
      },
    },
    {
      path: "/health",
      pipeline: {
        upstream: {
          type: "handler",
          handler: () =>
            new Response(JSON.stringify({ status: "ok" }), {
              headers: { "Content-Type": "application/json" },
            }),
        },
      },
    },
  ],
});

export default gateway.app;

This gateway works out of the box on Cloudflare Workers (export default gateway.app), Bun (export default gateway.app), Deno (Deno.serve(gateway.app.fetch)), or Node.js (via @hono/node-server).

With Cloudflare-specific features

When deploying to Cloudflare Workers, use the Cloudflare adapter to unlock Service Bindings, KV-backed rate limiting, and Durable Objects:

import { createGateway, jwtAuth, rateLimit, requestLog } from "@homegrower-club/stoma";
import { cloudflareAdapter } from "@homegrower-club/stoma/adapters/cloudflare";

type Env = {
  JWT_SECRET: string;
  USERS_SERVICE: Fetcher;
  RATE_LIMIT_KV: KVNamespace;
};

export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const gateway = createGateway({
      name: "my-api-gateway",
      basePath: "/api",
      adapter: cloudflareAdapter({
        rateLimitKv: env.RATE_LIMIT_KV,
        executionCtx: ctx,
        env,
      }),
      policies: [requestLog()],
      routes: [
        {
          path: "/users/*",
          pipeline: {
            policies: [
              jwtAuth({ secret: env.JWT_SECRET }),
              rateLimit({ max: 100, windowSeconds: 60 }),
            ],
            upstream: {
              type: "service-binding",
              service: "USERS_SERVICE",
            },
          },
        },
      ],
    });

    return gateway.app.fetch(request, env, ctx);
  },
};

Configuration

The gateway is configured entirely through TypeScript. The top-level GatewayConfig type drives everything:

interface GatewayConfig {
  name?: string;            // Gateway name, used in logs and metrics
  basePath?: string;        // Base path prefix for all routes (e.g. "/api")
  routes: RouteConfig[];    // Route definitions
  policies?: Policy[];      // Global policies applied to every route
  adapter?: GatewayAdapter; // Runtime adapter for stores and platform capabilities
  onError?: (error: Error, c: unknown) => Response | Promise<Response>;
}

Each route defines a path, optional methods, and a pipeline:

interface RouteConfig {
  path: string;                   // Hono path syntax: "/users/:id", "/files/*"
  methods?: HttpMethod[];         // ["GET", "POST", ...] -- defaults to all
  pipeline: PipelineConfig;       // Policy chain + upstream target
  metadata?: Record<string, unknown>;
}

A pipeline is an ordered chain of policies leading to an upstream:

interface PipelineConfig {
  policies?: Policy[];     // Executed in order before the upstream
  upstream: UpstreamConfig; // Where the request goes
}

Upstream Types

Three upstream types cover the common deployment patterns:

// URL proxy -- forward to any HTTP endpoint (works on all runtimes)
{ type: "url", target: "https://api.example.com", headers: { "X-Forwarded-By": "gateway" } }

// Service Binding -- zero-latency Worker-to-Worker routing (Cloudflare only)
{ type: "service-binding", service: "AUTH_SERVICE" }

// Inline handler -- respond directly without proxying (works on all runtimes)
{ type: "handler", handler: (c) => new Response("ok") }

Adapters

Adapters provide runtime-specific store implementations and platform capabilities. The gateway core is runtime-agnostic; adapters plug in distributed rate limiting, caching, background tasks, and service binding dispatch.

import { cloudflareAdapter } from "@homegrower-club/stoma/adapters/cloudflare";
import { nodeAdapter } from "@homegrower-club/stoma/adapters/node";
import { denoAdapter } from "@homegrower-club/stoma/adapters/deno";
import { bunAdapter } from "@homegrower-club/stoma/adapters/bun";
import { memoryAdapter } from "@homegrower-club/stoma/adapters/memory";

| Adapter | Stores | waitUntil | Service Bindings | Use case | |---------|--------|-------------|------------------|----------| | cloudflareAdapter | KV, Durable Objects, Cache API | Yes | Yes | Production on Cloudflare Workers | | nodeAdapter | In-memory defaults | No | No | Node.js servers via @hono/node-server | | denoAdapter | In-memory defaults | No | No | Deno / Deno Deploy | | bunAdapter | In-memory defaults | No | No | Bun servers | | memoryAdapter | In-memory (all stores) | No | No | Development, testing, single-instance |

No adapter is required. Without one, policies that need stores (rate limiting, caching, circuit breaking) fall back to in-memory defaults automatically.

Policies

Policies are the building blocks of gateway pipelines. They are middleware handlers with a name and a priority (lower numbers execute first).

Built-in Policies

| Category | Policies | |----------|----------| | Auth | jwtAuth, apiKeyAuth, basicAuth, oauth2, rbac, generateJwt, jws, generateHttpSignature, verifyHttpSignature | | Traffic | rateLimit, ipFilter, geoIpFilter, cache, sslEnforce, requestLimit, jsonThreatProtection, regexThreatProtection, trafficShadow, interrupt, dynamicRouting, httpCallout, resourceFilter | | Resilience | timeout, retry, circuitBreaker, latencyInjection | | Transform | cors, overrideMethod, assignAttributes, assignContent, requestTransform, responseTransform, requestValidation, jsonValidation | | Observability | requestLog, metricsReporter, assignMetrics, health | | Utility | proxy, mock |

Every policy accepts a skip function for conditional execution:

rateLimit({
  max: 100,
  skip: (c) => c.req.header("X-Internal") === "true",
})

Writing a Custom Policy

A policy is any object conforming to the Policy interface:

import type { Policy } from "@homegrower-club/stoma";

function requestTimer(): Policy {
  return {
    name: "request-timer",
    priority: 5,
    handler: async (c, next) => {
      const start = performance.now();
      await next();
      c.header("X-Response-Time", `${(performance.now() - start).toFixed(1)}ms`);
    },
  };
}

For a more structured approach, use the SDK:

import { definePolicy, Priority } from "@homegrower-club/stoma";

const requestTimer = definePolicy({
  name: "request-timer",
  priority: Priority.EARLY_TRANSFORM,
  handler: async ({ c, next }) => {
    const start = performance.now();
    await next();
    c.header("X-Response-Time", `${(performance.now() - start).toFixed(1)}ms`);
  },
});

Policy Execution Order

Policies execute in priority order (lowest number first), then in declaration order for policies sharing a priority. Global policies and route-level policies are merged and sorted together.

| Priority | Phase | Examples | |----------|-------|---------| | 0 | Observability | requestLog, assignMetrics | | 1 | Network filter | ipFilter, geoIpFilter, metricsReporter | | 5 | Early transform | cors, sslEnforce, requestLimit, latencyInjection | | 10 | Authentication | jwtAuth, apiKeyAuth, basicAuth, oauth2, rbac | | 20 | Rate limiting | rateLimit | | 30 | Circuit breaker | circuitBreaker | | 40 | Caching | cache | | 50 | Mid-pipeline | requestTransform, assignAttributes, generateJwt, dynamicRouting | | 85 | Timeout | timeout | | 90 | Retry | retry | | 92 | Response | responseTransform, trafficShadow, resourceFilter | | 95 | Proxy | proxy, generateHttpSignature | | 100 | Default | Custom policies | | 999 | Terminal | mock |

Documentation & Resources

Package Exports

| Import path | Contents | |-------------|----------| | @homegrower-club/stoma | createGateway, all policies, metrics, core types | | @homegrower-club/stoma/policies | Policies only | | @homegrower-club/stoma/sdk | definePolicy, Priority, test harness | | @homegrower-club/stoma/config | Config types + optional Zod validation (requires zod peer dep) | | @homegrower-club/stoma/adapters | All adapter factories | | @homegrower-club/stoma/adapters/cloudflare | Cloudflare adapter only | | @homegrower-club/stoma/adapters/node | Node.js adapter only | | @homegrower-club/stoma/adapters/deno | Deno adapter only | | @homegrower-club/stoma/adapters/bun | Bun adapter only | | @homegrower-club/stoma/adapters/memory | In-memory adapter (dev/test) |

Contributing

Contributions are welcome. Please open an issue to discuss proposed changes before submitting a pull request.

yarn install
yarn test
yarn typecheck

License

MIT