API Gateway Pattern
API Gateway acts as a single entry point for microservices. Learn to implement API Gateways for request routing, authentication, and rate limiting.
Challenge
Section titled “Challenge”Implement an API Gateway that provides unified access to microservices with authentication, rate limiting, and request routing.
Explanation
Section titled “Explanation”API Gateway Responsibilities
Section titled “API Gateway Responsibilities”- Routing: Direct requests to services
- Authentication: Verify requests
- Rate Limiting: Control traffic
- Transformation: Modify requests/responses
- Aggregation: Combine service responses
- Load Balancing: Distribute load
Benefits
Section titled “Benefits”- Single Entry Point: Simplified client interaction
- Cross-Cutting Concerns: Centralized logic
- Service Abstraction: Hide service complexity
- Protocol Translation: Different protocols
- API Versioning: Multiple API versions
Code Examples
Section titled “Code Examples”1. Express-Based API Gateway
Section titled “1. Express-Based API Gateway”const express = require('express');const httpProxy = require('express-http-proxy');const rateLimit = require('express-rate-limit');const jwt = require('jsonwebtoken');
const app = express();
// Service URLsconst services = { users: process.env.USER_SERVICE_URL || 'http://user-service:3001', orders: process.env.ORDER_SERVICE_URL || 'http://order-service:3002', products: process.env.PRODUCT_SERVICE_URL || 'http://product-service:3003', notifications: process.env.NOTIFICATION_SERVICE_URL || 'http://notification-service:3004',};
// Authentication middlewareconst authMiddleware = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1];
if (!token) { return res.status(401).json({ error: 'No token provided' }); }
try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); }};
// Rate limiterconst limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100,});
app.use(limiter);app.use(express.json());
// Health checkapp.get('/health', (req, res) => { res.json({ status: 'Gateway is running' });});
// Routes to servicesapp.use('/api/users', httpProxy(services.users, { proxyReqPathResolver: (req) => `/api/users${req.url}`,}));
app.use('/api/orders', authMiddleware, httpProxy(services.orders, { proxyReqPathResolver: (req) => `/api/orders${req.url}`,}));
app.use('/api/products', httpProxy(services.products, { proxyReqPathResolver: (req) => `/api/products${req.url}`,}));
// Request loggingapp.use((req, res, next) => { console.log(`${req.method} ${req.path} from ${req.ip}`); next();});
app.listen(8000, () => { console.log('API Gateway running on port 8000');});2. Advanced Gateway with Service Aggregation
Section titled “2. Advanced Gateway with Service Aggregation”class APIGateway { constructor() { this.services = new Map(); this.middleware = []; this.routes = []; }
registerService(name, url) { this.services.set(name, url); }
use(middleware) { this.middleware.push(middleware); }
route(path, method, service, options = {}) { this.routes.push({ path, method: method.toUpperCase(), service, ...options, }); }
async handleRequest(req, res) { // Find matching route const route = this.routes.find(r => r.path === req.path && r.method === req.method.toUpperCase() );
if (!route) { return res.status(404).json({ error: 'Not found' }); }
// Apply middleware for (const mw of this.middleware) { await new Promise((resolve, reject) => { mw(req, res, (err) => { if (err) reject(err); else resolve(); }); }); }
// Forward request const serviceUrl = this.services.get(route.service); const response = await this.forwardRequest(serviceUrl, req, route);
res.status(response.status).json(response.data); }
async forwardRequest(serviceUrl, req, route) { try { const response = await fetch(`${serviceUrl}${req.path}`, { method: req.method, headers: { ...req.headers, 'X-Gateway': 'true' }, body: req.body ? JSON.stringify(req.body) : null, });
const data = await response.json(); return { status: response.status, data }; } catch (error) { return { status: 503, data: { error: 'Service unavailable' }, }; } }
// Aggregate multiple service responses async aggregateServices(req, serviceList) { const results = {};
for (const service of serviceList) { try { const response = await fetch(`${this.services.get(service)}/data`); results[service] = await response.json(); } catch (error) { results[service] = { error: error.message }; } }
return results; }}
// Usageconst gateway = new APIGateway();
gateway.registerService('users', 'http://user-service:3001');gateway.registerService('orders', 'http://order-service:3002');
gateway.use((req, res, next) => { console.log(`${req.method} ${req.path}`); next();});
gateway.route('/api/users', 'GET', 'users');gateway.route('/api/orders', 'GET', 'orders');gateway.route('/api/dashboard', 'GET', null, { async handler(req, res) { const data = await gateway.aggregateServices(req, ['users', 'orders']); res.json(data); },});3. Kong API Gateway Configuration
Section titled “3. Kong API Gateway Configuration”_format_version: "1.1"_transform: true
services: - name: user-service url: http://user-service:3001 routes: - name: user-get paths: - /api/users methods: - GET - name: user-create paths: - /api/users methods: - POST plugins: - name: rate-limiting config: minute: 100 policy: local - name: jwt config: key_claim_name: sub - name: cors config: origins: - http://localhost:3000 methods: - GET - POST - PUT - DELETE
- name: order-service url: http://order-service:3002 routes: - name: order-get paths: - /api/orders methods: - GET plugins: - name: rate-limiting config: minute: 50 - name: jwt4. Middleware Pipeline
Section titled “4. Middleware Pipeline”class GatewayMiddlewarePipeline { constructor() { this.middlewares = []; }
use(middleware) { this.middlewares.push(middleware); }
async execute(req, res, context = {}) { let index = 0;
const next = async () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; await middleware(req, res, context, next); } };
await next(); }}
// Middleware implementationsconst authMiddleware = async (req, res, context, next) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'No token' }); } context.user = jwt.verify(token, process.env.JWT_SECRET); await next();};
const rateLimitMiddleware = async (req, res, context, next) => { const clientId = context.user?.id || req.ip; const limit = await checkRateLimit(clientId);
if (!limit) { return res.status(429).json({ error: 'Rate limit exceeded' }); }
await next();};
const loggingMiddleware = async (req, res, context, next) => { const start = Date.now(); await next(); const duration = Date.now() - start; console.log(`${req.method} ${req.path} ${duration}ms`);};
// Setup pipelineconst pipeline = new GatewayMiddlewarePipeline();pipeline.use(authMiddleware);pipeline.use(rateLimitMiddleware);pipeline.use(loggingMiddleware);5. Service Discovery Integration
Section titled “5. Service Discovery Integration”const Consul = require('consul');
class DynamicGateway { constructor() { this.consul = new Consul({ host: 'consul-server', port: 8500, }); this.services = new Map(); }
async discoverServices() { try { const services = await this.consul.catalog.service.list();
for (const [name, meta] of Object.entries(services)) { const instances = await this.consul.catalog.service.nodes(name); this.services.set(name, instances.map(i => `http://${i.ServiceAddress}:${i.ServicePort}`)); } } catch (error) { console.error('Service discovery error:', error); } }
selectInstance(serviceName) { const instances = this.services.get(serviceName); if (!instances || instances.length === 0) { throw new Error(`No instances for service ${serviceName}`); }
// Round-robin selection const index = Math.floor(Math.random() * instances.length); return instances[index]; }
async handleRequest(req, res, serviceName) { const instanceUrl = this.selectInstance(serviceName);
try { const response = await fetch(`${instanceUrl}${req.path}`, { method: req.method, headers: req.headers, body: req.body ? JSON.stringify(req.body) : null, });
res.status(response.status); res.json(await response.json()); } catch (error) { res.status(503).json({ error: 'Service unavailable' }); } }}
// Start discoveryconst gateway = new DynamicGateway();setInterval(() => gateway.discoverServices(), 30000);Best Practices
Section titled “Best Practices”-
Routing
- Clear path patterns
- Version handling
- Service abstraction
- Load balancing
-
Security
- Authenticate all requests
- Validate inputs
- Rate limiting
- HTTPS enforcement
-
Performance
- Connection pooling
- Caching
- Compression
- Monitoring
-
Reliability
- Service discovery
- Failover handling
- Circuit breakers
- Timeouts
-
Observability
- Request logging
- Tracing
- Metrics collection
- Error tracking
Real-World Applications
Section titled “Real-World Applications”- Microservices: Unified entry point
- Multiple Versions: API versioning
- Mobile Backends: BFF architecture
- Partner APIs: Controlled access
- Legacy Integration: Modernizing APIs