quired: boolean;
timeout: number;
}>;
}
export class AggregationGateway {
private circuitBreakers: Map<string, CircuitBreaker>;
constructor() {
this.circuitBreakers = new Map();
}
async aggregate(config: AggregationConfig): Promise<Record<string, any>> {
const promises = config.endpoints.map(async (endpoint) => {
const breaker = this.getCircuitBreaker(endpoint.key);
try {
const data = await breaker.execute(async () => {
return this.fetchWithTimeout(endpoint.url, endpoint.timeout);
});
return { key: endpoint.key, data, error: null };
} catch (err) {
if (endpoint.required) {
throw new Error(`Critical aggregation failed for ${endpoint.key}: ${err.message}`);
}
return { key: endpoint.key, data: null, error: err.message };
}
});
const results = await Promise.allSettled(promises);
return this.formatResults(results, config);
}
private formatResults(
results: PromiseSettledResult<any>[],
config: AggregationConfig
): Record<string, any> {
const aggregated: Record<string, any> = {};
let hasCriticalFailure = false;
results.forEach((result, index) => {
const endpoint = config.endpoints[index];
if (result.status === 'fulfilled') {
aggregated[endpoint.key] = result.value.data;
} else {
if (endpoint.required) {
hasCriticalFailure = true;
}
aggregated[endpoint.key] = null;
}
});
if (hasCriticalFailure) {
throw new Error('Aggregation failed due to critical dependency error.');
}
return aggregated;
}
private async fetchWithTimeout(url: string, ms: number): Promise<any> {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), ms);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(id);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
}
### 2. Protocol Translation Pattern
Gateway must bridge protocol gaps, such as translating REST to gRPC or handling WebSocket upgrades. This protects backend services from client protocol constraints.
```typescript
// protocol-translation.ts
import { IncomingMessage, ServerResponse } from 'http';
import { GrpcClient } from './grpc-client';
export class ProtocolTranslator {
private grpcClient: GrpcClient;
constructor(grpcClient: GrpcClient) {
this.grpcClient = grpcClient;
}
async handleRestToGrpc(req: IncomingMessage, res: ServerResponse): Promise<void> {
// 1. Parse REST request body/params
const restPayload = await this.parseRestPayload(req);
// 2. Map to gRPC message structure
const grpcRequest = this.mapToGrpcMessage(restPayload);
try {
// 3. Execute gRPC call
const grpcResponse = await this.grpcClient.call('UserService.GetProfile', grpcRequest);
// 4. Transform gRPC response to REST JSON
const restResponse = this.mapToRestJson(grpcResponse);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(restResponse));
} catch (err) {
this.handleGrpcError(err, res);
}
}
private mapToGrpcMessage(rest: any): any {
// Implementation specific mapping logic
return { userId: rest.id, fields: rest.fields || ['name', 'email'] };
}
}
3. Security Offloading Pattern
Centralizing JWT validation and rate limiting removes security logic from business services. This ensures consistent enforcement and reduces code duplication.
// security-offload.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import rateLimit from 'express-rate-limit';
export class SecurityMiddleware {
private jwtSecret: string;
private limiter: ReturnType<typeof rateLimit>;
constructor(jwtSecret: string) {
this.jwtSecret = jwtSecret;
this.limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
}
// Rate Limiting Middleware
applyRateLimit() {
return this.limiter;
}
// JWT Validation Middleware
validateJwt() {
return (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid token format' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, this.jwtSecret);
req.user = decoded; // Attach user context for downstream services
next();
} catch (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
};
}
}
Architecture Decisions and Rationale
- Statelessness: The gateway must be stateless to allow horizontal scaling. Session state belongs in Redis or distributed caches, not in gateway memory.
- Sync vs. Async: Aggregation should use synchronous calls with strict timeouts for request-response flows. For event-driven patterns, the gateway should route to message brokers (Kafka/RabbitMQ) without blocking the client.
- Plugin Architecture: Implement patterns as modular plugins. This allows dynamic loading of security rules or transformation logic without redeploying the gateway core.
Pitfall Guide
Production gateways fail due to predictable anti-patterns. Avoid these five critical mistakes based on real-world incident analysis.
1. The "Fat Gateway" Anti-Pattern
Mistake: Implementing business logic, data transformation, and workflow orchestration within the gateway.
Impact: The gateway becomes a distributed monolith. Deployment cycles slow down, and the gateway becomes a single point of failure for business rules.
Best Practice: Restrict the gateway to cross-cutting concerns. Business logic must reside in backend services. The gateway should only aggregate, translate, and secure.
2. Ignoring Cascading Failures
Mistake: Failing to implement circuit breakers and bulkheads at the gateway level.
Impact: When a backend service degrades, the gateway threads pile up waiting for responses. This exhausts gateway resources, causing total system outage even for healthy services.
Best Practice: Configure circuit breakers per upstream service. Use bulkheads to isolate traffic for critical vs. non-critical paths. Fail fast with cached data or default responses when breakers trip.
3. Stateful Gateway Instances
Mistake: Storing session data or rate limit counters in local memory.
Impact: Horizontal scaling breaks consistency. Users may experience session drops or rate limit resets when requests hit different gateway instances.
Best Practice: Use external state stores like Redis for session management and distributed rate limiting. Ensure the gateway process is purely compute.
4. Over-Optimization of Payloads
Mistake: Aggressively stripping fields or compressing data in the gateway to save bandwidth.
Impact: Clients receive incomplete data, leading to "chatty" follow-up requests that increase total latency. Compression adds CPU overhead that may negate bandwidth savings on modern networks.
Best Practice: Use BFF patterns for payload optimization rather than a unified gateway. Allow clients to request specific fields via GraphQL or query parameters, letting the gateway pass through the intent.
5. Security Misconfiguration on Internal Traffic
Mistake: Assuming traffic between the gateway and backend services is trusted and skipping validation.
Impact: If an attacker compromises the gateway or a backend service, lateral movement is unrestricted. Internal services become exposed to unauthorized access.
Best Practice: Implement Zero Trust. The gateway should validate tokens and forward identity headers (e.g., X-User-ID) signed with an internal shared secret. Backend services must verify the internal signature.
6. Versioning Nightmares
Mistake: Managing API versions by duplicating gateway routes and configuration files.
Impact: Configuration drift leads to security gaps in older versions. Maintenance overhead increases exponentially with each new version.
Best Practice: Use semantic versioning in URLs or headers. Implement versioning logic in the routing layer to map versions to backend service versions automatically. Deprecate old versions aggressively.
7. Lack of Observability
Mistake: Treating the gateway as a black box.
Impact: When latency spikes occur, teams cannot distinguish between gateway processing delays and backend service degradation.
Best Practice: Instrument the gateway with distributed tracing. Inject trace IDs into downstream requests. Expose metrics for request latency, error rates, and circuit breaker states.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Mobile Application | BFF Pattern | Reduces payload size, handles mobile-specific auth, optimizes battery/network usage. | High (Additional infra and dev effort). |
| Internal Microservices | Unified Gateway | Centralizes security, standardizes protocols, reduces service complexity. | Low (Shared infrastructure). |
| Legacy Monolith Migration | Strangler Fig Gateway | Routes new features to microservices while proxying legacy calls; enables incremental migration. | Medium (Routing complexity). |
| High-Traffic Public API | Edge Gateway + Caching | Offloads DDoS protection, reduces latency via edge caching, protects origin. | High (CDN and Edge compute costs). |
| IoT Device Management | Protocol Translation | Translates MQTT/CoAP to REST/HTTP; handles device authentication and telemetry routing. | Medium (Protocol adapter development). |
Configuration Template
This template demonstrates a declarative configuration structure for an aggregation and security gateway, suitable for implementation in a config-driven gateway or as a TypeScript config object.
// gateway.config.ts
export const gatewayConfig = {
routes: [
{
path: '/api/v1/dashboard',
method: 'GET',
pattern: 'AGGREGATION',
security: {
auth: 'JWT',
roles: ['user', 'admin'],
rateLimit: { max: 60, window: '1m' }
},
aggregation: {
endpoints: [
{
key: 'user_profile',
service: 'user-service',
path: '/profile',
required: true,
timeout: 500
},
{
key: 'recent_orders',
service: 'order-service',
path: '/orders/recent',
required: false,
timeout: 1000
},
{
key: 'notifications',
service: 'notification-service',
path: '/alerts',
required: false,
timeout: 300
}
],
fallback: {
strategy: 'PARTIAL_RESPONSE',
onCriticalFailure: 'HTTP_503'
}
}
},
{
path: '/api/v1/legacy',
method: 'ANY',
pattern: 'PROXY',
security: {
auth: 'NONE',
ipWhitelist: ['10.0.0.0/8']
},
proxy: {
target: 'http://legacy-monolith.internal:8080',
stripPath: true,
rewrite: { '^/api/v1/legacy': '/' }
}
}
],
global: {
headers: {
request: ['X-Request-Id', 'X-Trace-Id'],
response: ['X-Gateway-Version']
},
resilience: {
circuitBreaker: {
threshold: 50, // percent
timeout: 30000,
resetTimeout: 10000
},
retry: {
maxRetries: 2,
backoff: 'exponential'
}
}
}
};
Quick Start Guide
- Initialize Gateway Project: Create a new TypeScript project and install dependencies (
express, http-proxy-middleware, express-rate-limit, jsonwebtoken).
- Define Routes: Copy the configuration template and adapt the routes to your backend services. Implement the aggregation logic using the provided code structure.
- Apply Security Middleware: Integrate the
SecurityMiddleware class to apply JWT validation and rate limiting to protected routes.
- Deploy and Verify: Deploy the gateway container. Use
curl to test aggregation endpoints, verify rate limiting triggers after threshold, and confirm JWT rejection for unauthorized requests.
- Instrument Observability: Add middleware to inject trace IDs and export metrics to your monitoring stack (Prometheus/Datadog). Validate that downstream services receive the trace context.