Skip to content

Deploy with Node.js

Deploy your Stoma gateway on Node.js using @hono/node-server, Hono’s official Node.js adapter.

  1. Install dependencies

    Terminal window
    npm install hono @hono/node-server @vivero/stoma
  2. Create your gateway

    src/index.ts
    import { serve } from "@hono/node-server";
    import {
    createGateway,
    cors,
    requestLog,
    rateLimit,
    } from "@vivero/stoma";
    import { nodeAdapter } from "@vivero/stoma/adapters/node";
    const gateway = createGateway({
    name: "my-gateway",
    basePath: "/api",
    adapter: nodeAdapter(),
    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", ""),
    },
    },
    },
    ],
    });
    serve({ fetch: gateway.app.fetch, port: 3000 }, (info) => {
    console.log(`Gateway listening on http://localhost:${info.port}`);
    });
  3. Run

    Terminal window
    npx tsx src/index.ts

The nodeAdapter() provides in-memory stores for rate limiting, circuit breakers, and caching. It delegates to memoryAdapter() under the hood.

src/index.ts
import { serve } from "@hono/node-server";
import {
createGateway,
cors,
rateLimit,
cache,
} from "@vivero/stoma";
import { nodeAdapter } from "@vivero/stoma/adapters/node";
const adapter = nodeAdapter();
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: "url",
target: "https://api.example.com",
},
},
},
],
});
serve({ fetch: gateway.app.fetch, port: 3000 }, (info) => {
console.log(`Gateway listening on http://localhost:${info.port}`);
});
src/index.ts
import { serve } from "@hono/node-server";
import gateway from "./gateway";
serve({ fetch: gateway.app.fetch, port: 3000 }, (info) => {
console.log(`Listening on http://localhost:${info.port}`);
});
src/index.ts
import { readFileSync } from "node:fs";
import { serve } from "@hono/node-server";
import gateway from "./gateway";
serve({
fetch: gateway.app.fetch,
port: 443,
createServer: await import("node:https").then((m) => m.createServer),
serverOptions: {
cert: readFileSync("./cert.pem"),
key: readFileSync("./key.pem"),
},
});

For reverse proxy integration (nginx, Caddy):

src/index.ts
import { createServer } from "node:http";
import { serve } from "@hono/node-server";
import gateway from "./gateway";
const server = serve({
fetch: gateway.app.fetch,
createServer,
});
server.listen("/tmp/gateway.sock");
package.json
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
render.yaml
services:
- type: web
runtime: node
buildCommand: npm ci && npm run build
startCommand: node dist/index.js
envVars:
- key: NODE_ENV
value: production
fly.toml
app = "stoma-gateway"
[build]
builder = "heroku/buildpacks:22"
[env]
NODE_ENV = "production"
PORT = "3000"
[[services]]
internal_port = 3000
protocol = "tcp"
[[services.ports]]
port = 80
handlers = ["http"]
[[services.ports]]
port = 443
handlers = ["tls", "http"]
src/index.ts
const gateway = createGateway({
name: process.env.GATEWAY_NAME || "my-gateway",
basePath: process.env.BASE_PATH || "/api",
debug: process.env.DEBUG === "true",
});
serve({
fetch: gateway.app.fetch,
port: parseInt(process.env.PORT || "3000"),
});
.env
GATEWAY_NAME=my-gateway
PORT=3000
DEBUG=false