Skip to main content

Gateway

The gateway is the single entry point for all client traffic. Built on Hono, it mounts every service API in-process and serves the platform's static web UIs. In production it listens on port 3000.

In-Process Mounting

Each service exports a createApp() factory function that returns a Hono app instance. The gateway imports these factories and mounts them directly using gw.route():

import { createApp as createYPApi } from "../../../yellowpages/packages/api/src/index.js";
import { createApp as createPassportApi } from "../../../passport/packages/api/src/index.js";
// ... other services

const gw = new Hono();
gw.route("/", createYPApi());
gw.route("/", createPassportApi());

Because services are mounted in-process, there is no HTTP overhead between the gateway and service handlers. Each service's routes are prefixed (e.g., /api/v1/catalog/* for Yellow Pages, /api/v1/passport/* for Passport) within the service's own createApp().

Services are conditionally mounted based on feature flags. The gateway iterates over a service configuration array, checks isFeatureEnabled() for each, and only mounts enabled services. Disabled services are completely absent from the routing table.

Stage Proxy Mode

Stage is unique among services. It can run as either:

  1. In-process (default) -- The stage-cli package's createApp() is mounted like any other service.
  2. External proxy -- When the STAGE_PROXY_URL environment variable is set, the gateway proxies /api/v1/stage/* requests to the external Stage Next.js service.

The proxy mode includes automatic fallback: if the external service is unreachable, the gateway falls back to the in-process stage-cli handler. This applies to both API requests and health checks.

STAGE_PROXY_URL=https://stage.example.com

When set, requests to /api/v1/stage/* are forwarded to https://stage.example.com/api/v1/stage/*, preserving method, headers, and body. The host header is stripped and replaced with x-forwarded-host and x-forwarded-proto.

Static Web UI Serving

After all API routes are mounted, the gateway serves static files from its public/ directory. Each service's web SPA is built into a subdirectory (e.g., public/yellowpages/, public/pulse/).

The gateway configures SPA fallbacks for each enabled service: requests to /<service>/* that do not match a static file are served the service's index.html, allowing client-side routing to handle the path.

A catch-all fallback serves public/index.html for the platform dashboard.

Unknown API routes (/api/* paths that do not match any mounted service) return a JSON 404 response rather than falling through to the SPA.

Health Routes

The gateway exposes two health endpoints:

/healthz -- Gateway Health

A simple health check for the gateway itself, registered via healthRoute() from platform-core. Returns the service name and version. This route is always public and is not gated by authentication.

/api/v1/health -- Aggregated Health

Checks the health of all enabled services by calling each mounted service's /healthz endpoint in-process. Returns a list of service statuses:

{
"success": true,
"data": {
"services": [
{ "name": "yellowpages", "status": "ok", "version": "0.1.0" },
{ "name": "ledger", "status": "ok" },
{ "name": "stage", "status": "ok" }
]
}
}

For Stage, if the proxy URL is configured, the health check first attempts to reach the external service. If that fails, it falls back to the in-process health check.

Authentication Middleware

When auth environment variables are configured (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SESSION_SECRET), the gateway installs an auth middleware that protects all routes below it. See Authentication for details.

When these variables are unset (non-production environments), authentication is disabled and all routes are public.

Error Handling

The gateway uses errorHandler() from @shift/platform-core/api/error-handler, registered via gw.onError(). All uncaught errors are converted to the standard API response envelope:

{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "..."
}
}

Service code can throw HttpError subclasses (badRequest(), notFound(), conflict()) to return structured error responses with appropriate HTTP status codes.

Security Middleware

The gateway applies two global middleware handlers to all routes:

  • Security headers -- Sets standard security headers (X-Content-Type-Options, X-Frame-Options, etc.)
  • CORS -- Configures Cross-Origin Resource Sharing for browser-based API access.

Feature Flags Endpoint

The gateway exposes GET /api/v1/features (before auth middleware) that returns the current state of all feature flags, including whether each was set via environment variable, runtime override, or default. This endpoint is always available regardless of authentication state.