opticore-api-gateway
v1.0.0
Published
OptiCore API Gateway
Maintainers
Readme
opticore-api-gateway
A flexible, production-ready API Gateway built in TypeScript, powered by the Opticore ecosystem. It provides dynamic routing, load balancing, middleware chaining, circuit breaking, and service registry out of the box.
Table of Contents
- Features
- Installation
- Quick Start — Standalone mode
- Integration with opticore-webapp
- Configuration
- Load Balancing Strategies
- Middleware
- API Reference
- Types & Interfaces
- Advanced Usage
- Error Handling
- License
Features
- Dynamic Routing — Define routes with HTTP method, path, and target service(s)
- Load Balancing — Four strategies:
round-robin,weighted,random,least-connections - Service Registry — Automatic service health tracking and instance management
- Circuit Breaker — Per-route circuit breaker with configurable thresholds and reset timeouts
- Middleware Chain — Global and per-route middleware support (Express-compatible signature)
- Request Forwarding — Transparent proxying with header rewriting and tracing headers
- Logging — Built-in
LoggingMiddlewarewith timing and status code - Validation — Built-in
ValidationMiddlewareusingopticore-validator - opticore-webapp Integration — Drop-in replacement for
registerRouter()in any Opticore template project
Request lifecycle:
Incoming request
│
▼
Global middlewares (auth, rate-limit, logging, …)
│
▼
Route matched by method + path prefix
│
▼
Route-level middlewares
│
▼
Load balancer selects a healthy service instance
│
▼
Request forwarded to target with tracing headers
│
▼
Response sent back to clientInstallation
npm install opticore-api-gatewayRequires Node.js >= 16 and TypeScript >= 5.
Quick Start — Standalone mode
The gateway creates its own HTTP server and proxies requests to backend services.
import {
APIGateway,
AuthMiddleware,
RateLimitMiddleware,
LoggingMiddleware
} from 'opticore-api-gateway';
const gateway = new APIGateway({
port: 3000,
loadBalancer: 'round-robin',
enableLogging: true,
services: [
{
name: 'user-service',
url: 'http://localhost:4001',
healthCheck: '/health',
timeout: 5000,
retries: 3,
},
{
name: 'order-service',
url: 'http://localhost:4002',
healthCheck: '/health',
},
],
globalMiddlewares: [
new LoggingMiddleware('info'),
new RateLimitMiddleware(100, 60000),
],
routes: [
{
path: '/users',
method: 'get',
target: 'http://localhost:4001',
serviceName: 'user-service',
timeout: 5000,
},
{
path: '/orders',
method: 'post',
target: 'http://localhost:4002',
serviceName: 'order-service',
middlewares: [new AuthMiddleware(['my-secret-key'])],
circuitBreaker: {
failureThreshold: 5,
resetTimeout: 30000,
halfOpenMaxAttempts: 2,
},
},
],
});
await gateway.start();
// → API Gateway running on port 3000
// → Registered 2 routesIntegration with opticore-webapp
When you install opticore-api-gateway in an Opticore template project (e.g. opticore-api-restfull-template-mysql), the gateway can replace the registerRouter() call and manage all routing, middleware, and proxying.
How it works
gateway.getOpticoreRoutes() returns a TFeatureRoutes[] array that is directly passed to WebServerCore.onStartServer(). Each gateway route is mounted on the Express app with its global middlewares already embedded.
Step-by-step integration
1. Install the package
npm install opticore-api-gateway2. Replace registerRouter() in src/bootstrap/server/webApp.server.ts
import {
APIGateway,
AuthMiddleware,
RateLimitMiddleware,
LoggingMiddleware
} from 'opticore-api-gateway';
const gateway = new APIGateway({
port: 4200, // informational — Express port is managed by WebServerCore
services: [
{ name: 'user-service', url: 'http://user-service:4001' },
{ name: 'product-service', url: 'http://product-service:4002' },
],
routes: [
{
path: '/api/users',
method: 'get',
target: 'http://user-service:4001',
serviceName: 'user-service',
},
{
path: '/api/products',
method: 'get',
// Multiple targets → load balancing is activated
target: ['http://product-service-1:4002', 'http://product-service-2:4002'],
serviceName: 'product-service',
},
],
globalMiddlewares: [
new LoggingMiddleware('info'),
new RateLimitMiddleware(100, 60000),
new AuthMiddleware(['my-api-key']),
],
loadBalancer: 'round-robin',
enableLogging: true,
});
// Pass gateway routes instead of registerRouter()
webApp.onStartServer(gateway.getOpticoreRoutes(), dbConnection, dependenciesProvider);3. (Optional) Apply gateway middlewares globally via Express
If you want gateway middlewares to also run on routes outside the gateway (e.g. static files, error pages), use getExpressMiddleware():
// Before calling onStartServer:
expressApp.use(gateway.getExpressMiddleware());What getOpticoreRoutes() does internally
- Creates one Express Router per configured route, mounted at
config.path - Each router handles the
*wildcard — so/api/users/123,/api/users?page=2etc. are all captured - The handler restores
req.urltoreq.originalUrl(full path) before proxying so URL rewriting is correct - Global middlewares run before the proxy forward for every matched request
Configuration
IGatewayConfig interface
| Property | Type | Required | Description |
|---------------------|--------------------------|----------|-----------------------------------------------|
| port | number | ✅ | Port used by start() (standalone mode) |
| services | IServiceConfig[] | ✅ | Backend services to register |
| routes | IRouteConfig[] | ✅ | Route definitions |
| globalMiddlewares | any[] | ❌ | Middlewares applied to every request |
| loadBalancer | TLoadBalancingStrategy | ❌ | Default: round-robin |
| enableLogging | boolean | ❌ | Logs request duration when true |
| enableMetrics | boolean | ❌ | Reserved for future metrics collection |
IRouteConfig interface
| Property | Type | Required | Description |
|------------------|-------------------------|----------|---------------------------------------------------------|
| path | string | ✅ | URL path prefix (e.g. /api/users) |
| method | HttpMethod | ✅ | get, post, put, delete, patch, options… |
| target | string \| string[] | ✅ | Target URL(s). Array enables load balancing. |
| middlewares | any[] | ❌ | Route-specific middlewares (run after global ones) |
| timeout | number | ❌ | Request timeout in ms (default: 30000) |
| retries | number | ❌ | Number of retry attempts on failure |
| circuitBreaker | ICircuitBreakerConfig | ❌ | Circuit breaker configuration |
| serviceName | string | ❌ | Explicit service name override for registry lookup |
IServiceConfig interface
| Property | Type | Required | Description |
|---------------|----------|----------|------------------------------------------------|
| name | string | ✅ | Unique service identifier |
| url | string | ✅ | Base URL of the service |
| healthCheck | string | ❌ | Health check endpoint path (e.g. /health) |
| weight | number | ❌ | Weight for weighted load balancing strategy |
| timeout | number | ❌ | Default request timeout in ms |
| retries | number | ❌ | Default retry count |
ICircuitBreakerConfig interface
| Property | Type | Description |
|-----------------------|----------|--------------------------------------------------------|
| failureThreshold | number | Failures before the circuit opens |
| resetTimeout | number | Time in ms before transitioning to half-open |
| halfOpenMaxAttempts | number | Max probe requests allowed in half-open state |
Circuit states:
closed— Normal operation. Requests are forwarded.open— Service is unhealthy. Requests fail fast (503).half-open— Limited probing to test recovery.
Load Balancing Strategies
| Strategy | Description |
|---------------------|---------------------------------------------------------------------|
| round-robin | Distributes requests evenly across all healthy instances (default) |
| weighted | Routes more traffic to instances with a higher weight value |
| random | Selects a healthy instance at random |
| least-connections | Selects the instance with the fewest active connections |
const gateway = new APIGateway({
loadBalancer: 'least-connections',
// ...
});Multi-target route (load balancing across instances):
{
path: '/api',
method: 'get',
target: [
'http://service-1:4000',
'http://service-2:4000',
'http://service-3:4000',
],
serviceName: 'my-service',
}Middleware
All middlewares follow an Express-compatible signature:
type TMiddlewareFunction = (req: any, res: any, next: () => void) => void | Promise<void>;Global Middlewares
Applied to every matched request before the proxy forward:
const gateway = new APIGateway({
globalMiddlewares: [
new LoggingMiddleware('info'),
new RateLimitMiddleware(100, 60000),
new AuthMiddleware(['key-abc', 'key-xyz']),
],
// ...
});Add global middlewares at runtime:
gateway.addMiddleware((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});Route-Level Middlewares
Applied only to a specific route, after global middlewares:
{
path: '/admin',
method: 'get',
target: 'http://admin-service:5000',
middlewares: [
new AuthMiddleware(['admin-key']),
(req, res, next) => {
if (!req.headers['x-admin-token']) {
res.statusCode = 403;
res.end(JSON.stringify({ error: 'Forbidden' }));
return;
}
next();
},
],
}Built-in Middlewares
AuthMiddleware
Validates x-api-key header (or ?apiKey= query param):
import { AuthMiddleware } from 'opticore-api-gateway';
new AuthMiddleware(['secret-key-1', 'secret-key-2'])- Returns
401if no key is provided - Returns
403if the key is invalid
RateLimitMiddleware
Sliding-window rate limiting per client IP:
import { RateLimitMiddleware } from 'opticore-api-gateway';
new RateLimitMiddleware(100, 60000) // 100 requests per 60 secondsSets X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset response headers.
Returns 429 when the limit is exceeded.
LoggingMiddleware
Logs method, URL, status code, and duration on response finish:
import { LoggingMiddleware } from 'opticore-api-gateway';
new LoggingMiddleware('info') // 'info' | 'debug' | 'error'ValidationMiddleware
Validates request body (or query/params) using opticore-validator:
import { ValidationMiddleware } from 'opticore-api-gateway';
new ValidationMiddleware(mySchema, 'body') // 'body' | 'query' | 'params' | 'all'Returns 400 with field-level errors if validation fails.
Creating Custom Middlewares
Extend BaseMiddleware and implement handle():
import { BaseMiddleware, TMiddlewareFunction } from 'opticore-api-gateway';
export class CorsMiddleware extends BaseMiddleware {
handle(): TMiddlewareFunction {
return (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
next();
};
}
}API Reference
APIGateway
Main gateway class.
Constructor
new APIGateway(config: IGatewayConfig)Methods
| Method | Description |
|----------------------------------------------|--------------------------------------------------------------------------|
| start(): Promise<void> | Starts a standalone HTTP server on the configured port |
| stop(): Promise<void> | Gracefully shuts down the HTTP server |
| addRoute(config: IRouteConfig) | Dynamically registers a new route at runtime |
| addMiddleware(fn: TMiddlewareFunction) | Adds a global middleware at runtime |
| registerService(service: IServiceConfig) | Registers a new service in the service registry |
| getOpticoreRoutes(): TFeatureRoutes[] | Returns routes compatible with WebServerCore.onStartServer() |
| getExpressMiddleware() | Returns global middlewares as a single Express middleware function |
GatewayRoute
Encapsulates a single route and its Opticore integration.
Constructor
new GatewayRoute(config: IRouteConfig, handler: TRouteHandler, localLanguage: string)Methods
| Method | Return Type | Description |
|---------------------------------------------------------------|---------------------------|----------------------------------------------|
| getConfig() | IRouteConfig | Returns the route configuration |
| getStandaloneRoute(strategy?, options?) | Express Router | Returns the standalone Express router |
| createMultipleRouterConfig(controller) | IMultipleRouterConfig | Generates an Opticore multi-router config |
| createCollectionRouter(controller, routes) | void | Initializes a collection router |
| getMultipleRouteDefinition() | IMultipleRouteDefinition| Returns the collection router definition |
| GatewayRoute.createOpticoreRouteDefinition(path, handler) | { path, handler } | Static factory for Opticore route entries |
ServiceRegistry
Manages service instances and health state.
| Method | Description |
|--------------------------------------------|---------------------------------------------------------|
| registerService(service: IServiceConfig) | Registers a new service and starts health check |
| getHealthyInstances(serviceName: string) | Returns healthy (circuit-closed) instances |
| recordSuccess(serviceName, url) | Resets failure counters; may close circuit |
| recordFailure(serviceName, url) | Increments failure count; may open circuit |
| deregisterService(serviceName, url) | Removes an instance and clears its health check |
| getAllServices() | Returns the full service map |
LoadBalancer
Selects a service instance based on the configured strategy.
const lb = new LoadBalancer('weighted');
const instance = lb.selectInstance('user-service', healthyInstances);
lb.setStrategy('round-robin');HttpClient
Low-level HTTP/HTTPS client used internally to forward requests.
const client = new HttpClient();
const response = await client.request('http://service:4000/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' }),
timeout: 5000,
});
// response: { status, headers, body }Convenience methods: client.get(url), client.post(url, body), client.put(url, body), client.patch(url, body), client.delete(url).
Types & Interfaces
| Name | Description |
|--------------------------|--------------------------------------------------------------|
| TMiddlewareFunction | Express-compatible middleware signature |
| TGatewayContext | Opticore context extended with serviceUrl, serviceInstance, gatewayStartTime |
| TLoadBalancingStrategy | 'round-robin' \| 'weighted' \| 'random' \| 'least-connections' |
| IGatewayConfig | Root gateway configuration |
| IRouteConfig | Individual route configuration |
| IServiceConfig | Backend service declaration |
| IServiceInstance | Runtime service instance with health state |
| ICircuitBreakerConfig | Circuit breaker thresholds |
| IHttpRequestOptions | Options passed to HttpClient.request() |
| IHttpResponse | Response shape returned by HttpClient |
Advanced Usage
Adding a route at runtime
gateway.addRoute({
path: '/notifications',
method: 'get',
target: 'http://notification-service:6000',
serviceName: 'notification-service',
timeout: 3000,
retries: 2,
});Custom load balancer strategy
Implement ILoadBalancerStrategy to plug in your own logic:
import { ILoadBalancerStrategy } from 'opticore-api-gateway';
import { IServiceInstance } from 'opticore-api-gateway';
export class StickySessionStrategy implements ILoadBalancerStrategy {
selectInstance(serviceName: string, instances: IServiceInstance[]): IServiceInstance | null {
// Custom sticky-session logic based on session ID, user ID, etc.
return instances[0] ?? null;
}
}Accessing the gateway context
TGatewayContext extends Opticore's ICustomContext with gateway-specific fields:
type TGatewayContext = ICustomContext & {
serviceUrl?: string; // Resolved target URL
serviceInstance?: IServiceInstance; // Selected backend instance
gatewayStartTime?: number; // Request start timestamp (ms)
};Error Handling
| Scenario | HTTP Status | Response Body |
|----------------------------|-------------|--------------------------------------------------|
| No healthy instances | 503 | { "error": "Service unavailable" } |
| Load balancer returns null | 503 | { "error": "No healthy instances available" } |
| Forwarding / proxy error | 502 | { "error": "Bad Gateway", "message": "..." } |
| Middleware error | 500 | { "error": "Internal Server Error" } |
| Route not found | 404 | { "error": "Route not found" } |
| Missing API key | 401 | { "error": "API key required" } |
| Invalid API key | 403 | { "error": "Invalid API key" } |
| Rate limit exceeded | 429 | { "error": "Too many requests", "retryAfter": N } |
License
MIT
