A lightweight, config-driven API gateway built for the GatewayKit SWE take-home assessment.
GatewayKit reads a YAML configuration file and routes incoming HTTP requests to upstream services while applying gateway policies such as method filtering, authentication, rate limiting, retries, header transforms, load balancing, and timeout enforcement.
- YAML configuration loading
- Config path via CLI argument or
GATEWAY_CONFIG - Configurable gateway port from
gateway.port -
GET /healthavailable regardless of route config - Route prefix matching
- Longest-prefix route selection
- Method filtering with
405 Method Not Allowed -
Allowheader on method mismatch - JSON gateway error responses
- Basic proxying to upstream services
- Query string preservation
- Request body forwarding for body-capable methods
- Hard 5MB request body limit with
413 - Hop-by-hop request and response header stripping
-
strip_prefixpath rewriting - Timeout resolution from route, upstream, global, then default
- Per-attempt upstream timeout enforcement
-
502for upstream network/connect failures -
504for upstream timeouts - API key authentication
- Fixed-window rate limiting
-
Retry-Afteron429 -
global_rate_limitas the default inherited route policy - Per-route, per-method, per-IP/global rate limit buckets
-
sliding_windowaccepted and enforced with fixed-window behavior - Single upstream URLs
- Multiple upstream targets
- Round-robin target selection
- Weighted round-robin target selection using repeated slots
- Header transforms for request and response headers
- Dynamic header transform values:
$request_time,$response_time,$route_path - Retry policies for configured upstream HTTP statuses
- Retry of upstream network failures
- Fixed and exponential retry backoff
- Self-contained end-to-end test suite with mock upstreams
-
sliding_windowrate limiting is accepted but intentionally uses fixed-window state. - Retry support follows config, including for
POST, but does not add idempotency protection. - Load balancing supports weighted selection but does not perform active health checks.
- Response forwarding buffers upstream response bodies instead of streaming them.
- True sliding-window rate limiting
- Active upstream health checks
- Health-aware load balancing
- Circuit breaker
- Request body transformations
- Response body envelopes
- Streaming request/response proxying
- WebSocket support
- Distributed or multi-process rate limiting
- Hot config reload
- Production observability and structured logging
- Node.js 20+
- npm
npm installUsing TypeScript source directly during development:
npm run dev -- ./gateway.ymlUsing an environment variable:
GATEWAY_CONFIG=./gateway.yml npm run devUsing the compiled build:
npm run build
npm start -- ./gateway.ymlThe sample config listens on port 8080.
The health endpoint is always available regardless of configuration, auth, rate limits, or proxy state.
curl http://localhost:8080/healthExample response:
{
"status": "healthy",
"uptime_seconds": 42
}npm testTests automatically:
- Start mock upstream servers
- Start the gateway on ephemeral ports
- Execute end-to-end HTTP requests
- Verify config normalization, routing, proxying, rate limiting, auth, transforms, retries, and target selection
- Shut everything down
No external upstream services are required for the test suite.
Docker is optional, but the repository includes a Dockerfile for running the compiled gateway.
Build the image:
docker build -t gatewaykit .Run with the included gateway.yml:
docker run --rm -p 8080:8080 gatewaykitRun with a mounted config file:
docker run --rm -p 8080:8080 -v "$PWD/gateway.yml:/config/gateway.yml:ro" gatewaykit node dist/index.js /config/gateway.ymlNote: the sample gateway.yml points upstreams at localhost ports such as 3001 and 3002. Inside Docker, localhost means the container itself. For real proxy calls from a container, use upstream URLs reachable from the container network.
curl http://localhost:8080/api/userscurl \
-H "X-API-Key: sk_live_abc123" \
http://localhost:8080/api/internalRepeated requests beyond the configured limit return:
429 Too Many Requests
Retry-After: 60{
"error": "rate_limit_exceeded"
}Requests larger than 5MB return:
413 Payload Too Large{
"error": "payload_too_large"
}- Unknown fields are ignored for forward compatibility.
- Known advanced fields that are not fully implemented are accepted so the provided schema remains runnable.
request_transform.body,response_transform.body,health_check, andcircuit_breakerare accepted but deferred.sliding_windowrate limits are enforced as fixed-window limits for this take-home.- Retries follow route config, including non-idempotent methods like
POST; a production gateway would normally require idempotency keys or stricter retry policy controls. - Header names are treated case-insensitively by the underlying
Headersimplementation. - Rate limiting uses the socket remote address and ignores
X-Forwarded-For. - In-memory state is per gateway process. Distributed rate limiting and shared circuit state are out of scope.
This implementation intentionally prioritizes a reliable gateway core over complete feature coverage.
The most important omitted production features are:
- Streaming proxy support for very large request and response bodies
- Active upstream health checks
- Circuit breaker state
- True sliding-window rate limiting
- Request and response body transformations
- Structured logging, metrics, and tracing
- TLS termination and HTTP/2
These trade-offs are deliberate for the assessment time box: the implemented features are designed to be clean, testable, and straightforward for another engineer to extend.