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

@derian-cordoba/api-gateway

v1.1.0

Published

A generic, configuration-driven HTTP API gateway with per-route rate limiting, structured logging, and security headers

Readme

API Gateway

A generic, configuration-driven HTTP API gateway. Routes incoming requests to upstream services via a JSON config file or environment variable, with per-route rate limiting, request validation, structured logging, and full security headers out of the box.


Table of Contents


Features

  • Configuration-driven routing — define proxy routes in a JSON file, an environment variable, or both; changes take effect on restart with zero code changes
  • Per-route authentication — protect any route with a JWT Bearer token (HMAC or RSA/EC) or an API key; set enabled: false to bypass with zero overhead
  • Per-route rate limiting — each route can declare its own max requests / windowMs window, enforced by express-rate-limit
  • Startup validation — route config is validated with Zod at boot time; the process exits with a descriptive error rather than silently misbehaving
  • Structured loggingpino + pino-http emit newline-delimited JSON in production and human-readable output (via pino-pretty) in development
  • Security headers — full helmet defaults applied to every response (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, etc.)
  • Configurable CORS — origins, methods, and allowed headers controlled via environment variables
  • Health check endpointGET /health returns uptime, version, and timestamp; always available regardless of configured routes
  • Optional URL prefix — mount all routes under a shared prefix (e.g. /api/v1) via GATEWAY_PREFIX
  • Body forwarding — JSON bodies on POST, PUT, and PATCH requests are correctly forwarded to upstreams (fixRequestBody)
  • Graceful shutdownSIGINT and uncaughtException handlers stop the server cleanly before exiting

Requirements

  • Node.js 18+
  • pnpm 10+

Getting Started

# 1. Clone and install
git clone <repo-url>
cd api-gateway
pnpm install

# 2. Create your env file
cp .env.example .env

# 3. Create a routes config (see Route Configuration below)
cp examples/basic/routes.json routes.json   # or write your own

# 4. Start in development mode (hot-reload)
pnpm dev

Configuration

Environment Variables

Copy .env.example to .env and edit as needed.

Server

| Variable | Default | Description | |---|---|---| | GATEWAY_PORT | 3000 | Port the gateway listens on. Takes priority over PORT. | | PORT | 3000 | Fallback port when GATEWAY_PORT is not set. | | GATEWAY_PREFIX | (none) | Optional path prefix for all routes. Example: /api/v1 makes proxy routes reachable at /api/v1/<baseURL> and the health check at /api/v1/health. |

Logging

| Variable | Default | Description | |---|---|---| | NODE_ENV | development | Set to production to disable pino-pretty and emit newline-delimited JSON. | | LOG_LEVEL | info | Pino log level: trace · debug · info · warn · error · fatal. |

CORS

| Variable | Default | Description | |---|---|---| | CORS_ORIGINS | * | Comma-separated list of allowed origins. Use * to allow all. | | CORS_METHODS | GET,POST,PUT,DELETE,PATCH,OPTIONS | Comma-separated list of allowed HTTP methods. | | CORS_HEADERS | Content-Type,Authorization | Comma-separated list of allowed request headers. |

Routes

| Variable | Default | Description | |---|---|---| | ROUTES_FILE_PATH | routes.json | Path to the JSON route config file, relative to process.cwd(). | | ROUTES | (none) | Inline route definitions as a JSON array. Merged with ROUTES_FILE_PATH. Useful for containerised deployments where injecting a file is inconvenient. |

Authentication

| Variable | Default | Description | |---|---|---| | JWT_SECRET | (none) | Fallback HMAC signing secret used when a JWT route has no inline secret field. | | JWT_PUBLIC_KEY | (none) | Fallback PEM public key used when a JWT route has no inline publicKey field. Takes precedence over JWT_SECRET. |


Route Configuration

Routes are defined as a JSON array. Each entry is a Gateway object:

{
  baseURL:    string     // required — path prefix to match, must start with "/"
  proxy:      Proxy      // required — upstream proxy settings
  rateLimit?: RateLimit  // optional — per-route rate limiting
}

Proxy

| Field | Type | Required | Description | |---|---|---|---| | target | string | ✅ | Target upstream URL (must be a valid URL). | | changeOrigin | boolean | — | Rewrite the Host header to the target origin. | | pathRewrite | { [pattern]: replacement } | — | Regex path rewrite rules applied before forwarding. | | headers | { [name]: value } | — | Extra headers added to every forwarded request. | | isSecure | boolean | — | Verify the upstream TLS certificate. | | method | string | — | Override the HTTP method forwarded to the upstream. | | timeout | number | — | Proxy request timeout in milliseconds. |

RateLimit

| Field | Type | Required | Description | |---|---|---|---| | max | number | ✅ | Maximum number of requests allowed per window. | | windowMs | number | ✅ | Time window in milliseconds. | | statusCode | number | — | HTTP status returned when the limit is exceeded (default: 429). | | message | string | — | Response message when the limit is exceeded (default: "Too many requests"). |

Responses include standard RateLimit-* headers (RFC draft-8).

Auth

Adds authentication middleware to a route. When enabled is false the middleware is a no-op passthrough — no overhead, no token check.

Two strategies are supported, selected with the strategy field.

"jwt" — Bearer token validation

| Field | Type | Required | Description | |---|---|---|---| | enabled | boolean | ✅ | true to enforce, false to bypass. | | strategy | "jwt" | ✅ | — | | secret | string | — | Shared secret for HMAC algorithms (HS256, HS384, HS512). Falls back to JWT_SECRET env var. | | publicKey | string | — | PEM-encoded public key or X.509 certificate for asymmetric algorithms (RS256, RS384, RS512, ES256 …). Falls back to JWT_PUBLIC_KEY env var. Takes precedence over secret when both are present. | | algorithms | string[] | — | Explicit algorithm allowlist. Defaults to ["RS256"] when publicKey is used, ["HS256"] otherwise. Recommended to prevent algorithm-confusion attacks. |

The gateway expects the token in the Authorization: Bearer <token> header and returns 401 on a missing, malformed, or invalid token.

"apiKey" — Header-based API key

| Field | Type | Required | Description | |---|---|---|---| | enabled | boolean | ✅ | true to enforce, false to bypass. | | strategy | "apiKey" | ✅ | — | | keys | string[] | ✅ | List of valid API keys. At least one entry required. | | header | string | — | Header name to read the key from (default: x-api-key). |

Returns 401 when the header is absent or the value is not in keys.

Example routes.json

[
  {
    "baseURL": "/users",
    "proxy": {
      "target": "http://users-service:3001",
      "changeOrigin": true,
      "pathRewrite": { "^/users": "" }
    },
    "rateLimit": {
      "max": 100,
      "windowMs": 60000,
      "statusCode": 429,
      "message": "Too many requests. Please try again in a minute."
    }
  },
  {
    "baseURL": "/orders",
    "proxy": {
      "target": "http://orders-service:3002",
      "changeOrigin": true,
      "pathRewrite": { "^/orders": "" }
    },
    "auth": {
      "enabled": true,
      "strategy": "jwt"
    }
  },
  {
    "baseURL": "/reports",
    "proxy": {
      "target": "http://reports-service:3003",
      "changeOrigin": true,
      "pathRewrite": { "^/reports": "" }
    },
    "auth": {
      "enabled": true,
      "strategy": "apiKey",
      "keys": ["key-service-alpha-123", "key-service-beta-456"]
    }
  }
]

Config is validated with Zod at startup. If any route is invalid the process exits immediately with a detailed per-field error message.

Routes are loaded from two sources and merged:

  1. ROUTES_FILE_PATH — JSON file on disk (missing file is a warning, not an error)
  2. ROUTES — JSON array in an environment variable

Authentication

Authentication is optional and configured per route via the auth field. The middleware is applied before rate limiting and proxying. When enabled: false the handler is a single no-op function — zero overhead on unprotected routes.

JWT

Protect a route with a Bearer token. The gateway validates the token signature; your upstream receives the request only if verification passes.

{
  "baseURL": "/orders",
  "proxy": { "target": "http://orders-service:3002", "changeOrigin": true },
  "auth": {
    "enabled": true,
    "strategy": "jwt"
  }
}

The signing key is resolved in this order:

  1. publicKey field in the route config (PEM — use for RS256 / ES256)
  2. JWT_PUBLIC_KEY environment variable
  3. secret field in the route config (string — use for HS256)
  4. JWT_SECRET environment variable

publicKey always takes precedence over secret. If neither is present the gateway returns 401.

HMAC (HS256) — shared secret:

# .env
JWT_SECRET=super-secret-key-change-in-production
TOKEN=$(curl -s -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"password123"}' | jq -r '.token')

curl http://localhost:3000/orders \
  -H "Authorization: Bearer $TOKEN"

RSA (RS256) — public/private key pair:

{
  "auth": {
    "enabled": true,
    "strategy": "jwt",
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjAN...\n-----END PUBLIC KEY-----",
    "algorithms": ["RS256"]
  }
}

API Key

Protect a route with a pre-shared key delivered in a request header.

{
  "baseURL": "/reports",
  "proxy": { "target": "http://reports-service:3003", "changeOrigin": true },
  "auth": {
    "enabled": true,
    "strategy": "apiKey",
    "header": "x-api-key",
    "keys": ["key-service-alpha-123", "key-service-beta-456"]
  }
}
# Valid key → 200
curl http://localhost:3000/reports \
  -H "x-api-key: key-service-alpha-123"

# Missing or wrong key → 401
curl http://localhost:3000/reports

Multiple keys in keys let you rotate credentials without downtime — add the new key, deploy, then remove the old one.


Running the Gateway

Development (hot-reload)

pnpm dev

Uses ts-node-dev to transpile on the fly and restart on file changes. Logs are pretty-printed via pino-pretty.

Production

# Compile TypeScript
pnpm build

# Run the compiled output
pnpm start

In production (NODE_ENV=production) logs are emitted as newline-delimited JSON suitable for log aggregators (Datadog, Loki, CloudWatch, etc.).


Health Check

The gateway exposes a built-in health check endpoint that is always available, independent of the configured proxy routes.

GET /health

If GATEWAY_PREFIX is set, the endpoint is available at <GATEWAY_PREFIX>/health.

Response 200 OK:

{
  "status": "ok",
  "uptime": 42,
  "version": "1.0.0",
  "timestamp": "2026-06-18T00:00:00.000Z"
}

| Field | Description | |---|---| | status | Always "ok" when the process is alive. | | uptime | Seconds since the gateway process started. | | version | Value of npm_package_version (set automatically by npm/pnpm). | | timestamp | ISO 8601 timestamp of the response. |


Project Structure

src/apps/api-gateway/
├── index.ts                  # Entry point — bootstraps the app, registers process signals
├── App.ts                    # Thin lifecycle wrapper (start / stop)
├── Server.ts                 # HTTP server creation and prefix mounting
├── logger.ts                 # Pino logger singleton
│
├── config/
│   ├── app-env.ts            # Aggregates all config modules into a single AppEnv object
│   ├── env/config.ts         # NODE_ENV → isDev flag
│   ├── gateway/config.ts     # GATEWAY_PORT, GATEWAY_PREFIX
│   ├── cors/config.ts        # CORS_ORIGINS, CORS_METHODS, CORS_HEADERS
│   ├── routes/config.ts      # ROUTES_FILE_PATH
│   └── auth/config.ts        # JWT_SECRET, JWT_PUBLIC_KEY
│
├── middleware/
│   ├── authMiddleware.ts     # Factory — returns the right strategy or a no-op
│   └── auth/
│       ├── AuthStrategy.ts   # Interface (Strategy pattern)
│       ├── JwtAuthStrategy.ts    # JWT Bearer token validation (HMAC + RSA/EC)
│       └── ApiKeyAuthStrategy.ts # Header-based API key validation
│
├── routes/
│   ├── Router.ts             # Middleware pipeline (logging → security → CORS → body → proxy → errors)
│   ├── ProxyManager.ts       # Reads, validates, and registers proxy routes
│   ├── RouteValidator.ts     # Zod schemas for Gateway / Proxy / RateLimit / Auth types
│   └── HealthRouter.ts       # GET /health handler
│
└── types/
    ├── gateway.d.ts          # Gateway type
    ├── proxy.d.ts            # Proxy type
    ├── rate-limit.d.ts       # RateLimit type
    └── auth.d.ts             # Auth type (JwtAuth | ApiKeyAuth)

Example Project

examples/ contains a self-contained demo that starts five upstream services and one gateway covering all features: rate limiting, JWT auth, and API key auth.

pnpm example

This copies examples/.env to the project root and starts everything. Once running:

| Endpoint | Auth | Description | |---|---|---| | http://localhost:3000/health | — | Gateway health check | | http://localhost:3000/users | — | Users service (rate limited) | | http://localhost:3000/products | — | Products service (rate limited) | | http://localhost:3000/auth/login | — | Issues JWT tokens | | http://localhost:3000/orders | JWT Bearer | Orders service | | http://localhost:3000/reports | API key | Reports service |

Public routes

curl http://localhost:3000/users
curl http://localhost:3000/products/1

JWT-protected route (/orders)

# 1. Log in to get a token (users: alice/password123, bob/password456)
TOKEN=$(curl -s -X POST http://localhost:3000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"password123"}' | jq -r '.token')

# 2. Access the protected route
curl http://localhost:3000/orders \
  -H "Authorization: Bearer $TOKEN"

# 3. Create an order
curl -X POST http://localhost:3000/orders \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userId":1,"items":[{"productId":2,"qty":1}],"total":24.99}'

API-key-protected route (/reports)

# Valid keys: key-service-alpha-123  ·  key-service-beta-456

curl http://localhost:3000/reports \
  -H "x-api-key: key-service-alpha-123"

curl http://localhost:3000/reports/sales \
  -H "x-api-key: key-service-beta-456"

Individual example directories

Each sub-directory is also usable as a standalone reference:

| Directory | Description | |---|---| | examples/basic/ | Rate limiting only — Users + Products services | | examples/jwt-auth/ | JWT auth — Auth service + Orders service | | examples/api-key-auth/ | API key auth — Reports service |


Architecture

Request lifecycle

Client
  │
  ▼
Express app
  │
  ├─ pino-http              structured request/response logging
  ├─ helmet                 security headers (CSP, HSTS, X-Frame-Options, …)
  ├─ cors                   configurable origin / method / header policy
  ├─ express.json           body parsing
  ├─ compression            gzip response compression
  │
  ├─ GET /health            health check — short-circuits here
  │
  ├─ authMiddleware         per-route — JWT or API key check → 401 on failure
  ├─ express-rate-limit     per-route request throttling → 429 on exceeded
  ├─ http-proxy-middleware  proxies request to upstream, forwards body
  │
  └─ error handler          catches unhandled errors → 500 JSON response

Startup sequence

index.ts
  │
  ├─ dotenv.config()           load .env before any module reads process.env
  ├─ new App()
  │    └─ new Server()
  │         ├─ appEnv resolved  config modules read from process.env
  │         └─ new Router()
  │
  └─ app.start()
       ├─ router.init()          async: reads routes file, validates, registers middleware
       └─ httpServer.listen()    starts accepting connections only after routes are ready

Route loading

Routes are loaded from two sources at startup and merged into a single array before validation:

ROUTES_FILE_PATH (JSON file)  ──┐
                                ├──► merge ──► Zod validation ──► register proxy routes
ROUTES (env var JSON array)   ──┘

If either source is missing or contains invalid JSON it is skipped with a warning. If the merged result fails Zod validation, the process exits with a descriptive error.