API Gateway Pattern
An API Gateway is the single entry point for all client requests to your microservices. It handles cross-cutting concerns and provides a unified interface.Learning Objectives:
- Understand API Gateway responsibilities
- Implement routing and load balancing
- Add authentication and authorization
- Implement rate limiting and throttling
- Build request aggregation patterns
Why API Gateway?
Without API Gateway
Copy
CLIENT
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ User │ │ Order │ │ Product │
│ Service │ │ Service │ │ Service │
└─────────┘ └─────────┘ └─────────┘
Problems:
• Client needs to know all service URLs
• Each service implements auth, rate limiting
• No unified error handling
• CORS issues
• Multiple round trips
• Exposing internal services
With API Gateway
Copy
CLIENT
│
▼
┌─────────────┐
│ API GATEWAY │
│ ─────────── │
│ • Routing │
│ • Auth │
│ • Rate Limit│
│ • Caching │
│ • Logging │
└──────┬──────┘
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ User │ │ Order │ │ Product │
│ Service │ │ Service │ │ Service │
└─────────┘ └─────────┘ └─────────┘
Benefits:
• Single entry point
• Centralized cross-cutting concerns
• Protocol translation
• Request aggregation
• Service discovery abstraction
API Gateway Responsibilities
Copy
┌─────────────────────────────────────────────────────────────────────────────┐
│ API GATEWAY RESPONSIBILITIES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ ROUTING │ │ AUTHENTICATION │ │ RATE LIMITING │ │
│ │ ─────────────────── │ │ ─────────────────── │ │ ─────────────────── │ │
│ │ • Path-based │ │ • JWT validation │ │ • Per-user limits │ │
│ │ • Method-based │ │ • API key check │ │ • Per-IP limits │ │
│ │ • Header-based │ │ • OAuth 2.0 │ │ • Global limits │ │
│ │ • Load balancing │ │ • Session mgmt │ │ • Burst handling │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ REQUEST TRANSFORM │ │ CACHING │ │ AGGREGATION │ │
│ │ ─────────────────── │ │ ─────────────────── │ │ ─────────────────── │ │
│ │ • Header injection │ │ • Response caching │ │ • Combine responses │ │
│ │ • Body transform │ │ • Cache invalidation│ │ • Reduce round trips│ │
│ │ • Protocol convert │ │ • Cache keys │ │ • BFF pattern │ │
│ │ • Versioning │ │ • TTL management │ │ • GraphQL facade │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ MONITORING │ │ CIRCUIT BREAKER │ │ SECURITY │ │
│ │ ─────────────────── │ │ ─────────────────── │ │ ─────────────────── │ │
│ │ • Request logging │ │ • Failure detection │ │ • CORS handling │ │
│ │ • Metrics │ │ • Fallback routes │ │ • WAF integration │ │
│ │ • Tracing │ │ • Recovery │ │ • IP filtering │ │
│ │ • Alerting │ │ • Health checks │ │ • TLS termination │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Building an API Gateway with Express
Basic Gateway Structure
Copy
// gateway/src/app.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true
}));
// Request parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Logging
app.use(morgan(':method :url :status :response-time ms - :res[content-length]'));
// Request ID middleware
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] || generateRequestId();
res.setHeader('X-Request-ID', req.id);
next();
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Import routes
const authMiddleware = require('./middleware/auth');
const rateLimiter = require('./middleware/rateLimiter');
const routes = require('./routes');
// Apply middleware
app.use(rateLimiter);
app.use('/api', authMiddleware);
app.use('/api', routes);
// Error handling
app.use((err, req, res, next) => {
console.error(`Error [${req.id}]:`, err);
const statusCode = err.statusCode || 500;
const message = err.expose ? err.message : 'Internal server error';
res.status(statusCode).json({
error: {
code: err.code || 'INTERNAL_ERROR',
message,
requestId: req.id
}
});
});
module.exports = app;
Service Routing
Copy
// gateway/src/routes/index.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const router = express.Router();
// Service URLs from environment
const 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',
payments: process.env.PAYMENT_SERVICE_URL || 'http://payment-service:3004',
notifications: process.env.NOTIFICATION_SERVICE_URL || 'http://notification-service:3005'
};
// Proxy configuration factory
const createProxy = (target, pathRewrite = {}) => {
return createProxyMiddleware({
target,
changeOrigin: true,
pathRewrite,
timeout: 30000,
proxyTimeout: 30000,
onProxyReq: (proxyReq, req) => {
// Forward authentication info
if (req.user) {
proxyReq.setHeader('X-User-ID', req.user.id);
proxyReq.setHeader('X-User-Email', req.user.email);
proxyReq.setHeader('X-User-Roles', JSON.stringify(req.user.roles));
}
// Forward correlation headers
proxyReq.setHeader('X-Request-ID', req.id);
proxyReq.setHeader('X-Correlation-ID', req.correlationId || req.id);
},
onProxyRes: (proxyRes, req, res) => {
// Add security headers
proxyRes.headers['X-Content-Type-Options'] = 'nosniff';
proxyRes.headers['X-Frame-Options'] = 'DENY';
},
onError: (err, req, res) => {
console.error(`Proxy error [${req.id}]:`, err.message);
if (!res.headersSent) {
res.status(503).json({
error: {
code: 'SERVICE_UNAVAILABLE',
message: 'Service temporarily unavailable',
requestId: req.id
}
});
}
}
});
};
// Route to services
router.use('/users', createProxy(services.users, { '^/api/users': '' }));
router.use('/orders', createProxy(services.orders, { '^/api/orders': '' }));
router.use('/products', createProxy(services.products, { '^/api/products': '' }));
router.use('/payments', createProxy(services.payments, { '^/api/payments': '' }));
router.use('/notifications', createProxy(services.notifications, { '^/api/notifications': '' }));
module.exports = router;
Authentication Middleware
Copy
// gateway/src/middleware/auth.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;
const publicPaths = [
{ method: 'POST', path: '/api/users/register' },
{ method: 'POST', path: '/api/users/login' },
{ method: 'GET', path: '/api/products' },
{ method: 'GET', path: /^\/api\/products\/[\w-]+$/ }
];
const isPublicPath = (method, path) => {
return publicPaths.some(route => {
const methodMatch = route.method === method || route.method === '*';
const pathMatch = route.path instanceof RegExp
? route.path.test(path)
: route.path === path;
return methodMatch && pathMatch;
});
};
const authMiddleware = async (req, res, next) => {
// Skip auth for public paths
if (isPublicPath(req.method, req.path)) {
return next();
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: 'Missing or invalid authorization header'
}
});
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = {
id: decoded.sub,
email: decoded.email,
roles: decoded.roles || [],
permissions: decoded.permissions || []
};
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: {
code: 'TOKEN_EXPIRED',
message: 'Authentication token has expired'
}
});
}
return res.status(401).json({
error: {
code: 'INVALID_TOKEN',
message: 'Invalid authentication token'
}
});
}
};
// Role-based access control
const requireRoles = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
error: { code: 'UNAUTHORIZED', message: 'Authentication required' }
});
}
const hasRole = roles.some(role => req.user.roles.includes(role));
if (!hasRole) {
return res.status(403).json({
error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
});
}
next();
};
};
module.exports = authMiddleware;
module.exports.requireRoles = requireRoles;
Rate Limiting
Copy
// gateway/src/middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
// Global rate limiter
const globalLimiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redis.call(...args)
}),
windowMs: 60 * 1000, // 1 minute
max: 1000, // 1000 requests per minute globally
message: {
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests, please try again later'
}
},
standardHeaders: true,
legacyHeaders: false
});
// Per-user rate limiter
const userLimiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redis.call(...args)
}),
windowMs: 60 * 1000,
max: 100, // 100 requests per minute per user
keyGenerator: (req) => {
return req.user?.id || req.ip;
},
skip: (req) => {
// Skip rate limiting for internal services
return req.headers['x-internal-service'] === process.env.INTERNAL_SECRET;
}
});
// Endpoint-specific limiters
const createEndpointLimiter = (options) => {
return rateLimit({
store: new RedisStore({
sendCommand: (...args) => redis.call(...args)
}),
windowMs: options.windowMs || 60 * 1000,
max: options.max || 10,
keyGenerator: (req) => `${req.user?.id || req.ip}:${req.path}`,
message: {
error: {
code: 'ENDPOINT_RATE_LIMIT',
message: options.message || 'Rate limit exceeded for this endpoint'
}
}
});
};
// Specific endpoint limiters
const authLimiter = createEndpointLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 login attempts
message: 'Too many login attempts, please try again later'
});
const passwordResetLimiter = createEndpointLimiter({
windowMs: 60 * 60 * 1000, // 1 hour
max: 3,
message: 'Too many password reset requests'
});
module.exports = {
globalLimiter,
userLimiter,
authLimiter,
passwordResetLimiter,
createEndpointLimiter
};
Request Aggregation
Copy
// gateway/src/aggregation/orderDetails.js
const axios = require('axios');
class OrderAggregator {
constructor(services) {
this.services = services;
}
// Aggregate order details from multiple services
async getOrderDetails(orderId, userId) {
try {
// Parallel requests to multiple services
const [order, user, products] = await Promise.all([
this.getOrder(orderId),
this.getUser(userId),
this.getOrderProducts(orderId)
]);
// Aggregate response
return {
order: {
id: order.id,
status: order.status,
total: order.total,
createdAt: order.createdAt
},
customer: {
id: user.id,
name: user.name,
email: user.email
},
items: products.map(p => ({
productId: p.id,
name: p.name,
price: p.price,
quantity: order.items.find(i => i.productId === p.id)?.quantity
})),
shipping: order.shippingAddress,
timeline: await this.getOrderTimeline(orderId)
};
} catch (error) {
throw new AggregationError('Failed to aggregate order details', error);
}
}
async getOrder(orderId) {
const response = await axios.get(`${this.services.orders}/orders/${orderId}`);
return response.data;
}
async getUser(userId) {
const response = await axios.get(`${this.services.users}/users/${userId}`);
return response.data;
}
async getOrderProducts(orderId) {
const order = await this.getOrder(orderId);
const productIds = order.items.map(i => i.productId);
const response = await axios.post(
`${this.services.products}/products/batch`,
{ ids: productIds }
);
return response.data;
}
async getOrderTimeline(orderId) {
// Get events from multiple sources
const [orderEvents, shippingEvents] = await Promise.allSettled([
axios.get(`${this.services.orders}/orders/${orderId}/events`),
axios.get(`${this.services.shipping}/shipments/order/${orderId}/events`)
]);
const events = [];
if (orderEvents.status === 'fulfilled') {
events.push(...orderEvents.value.data);
}
if (shippingEvents.status === 'fulfilled') {
events.push(...shippingEvents.value.data);
}
return events.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
}
// Dashboard aggregation
async getDashboard(userId) {
const [orders, cart, recommendations, notifications] = await Promise.allSettled([
axios.get(`${this.services.orders}/users/${userId}/orders?limit=5`),
axios.get(`${this.services.orders}/users/${userId}/cart`),
axios.get(`${this.services.products}/users/${userId}/recommendations`),
axios.get(`${this.services.notifications}/users/${userId}/unread`)
]);
return {
recentOrders: orders.status === 'fulfilled' ? orders.value.data : [],
cart: cart.status === 'fulfilled' ? cart.value.data : null,
recommendations: recommendations.status === 'fulfilled' ? recommendations.value.data : [],
unreadNotifications: notifications.status === 'fulfilled' ? notifications.value.data.length : 0
};
}
}
// Aggregation route
router.get('/aggregate/orders/:orderId', async (req, res, next) => {
try {
const aggregator = new OrderAggregator(services);
const details = await aggregator.getOrderDetails(
req.params.orderId,
req.user.id
);
res.json(details);
} catch (error) {
next(error);
}
});
router.get('/aggregate/dashboard', async (req, res, next) => {
try {
const aggregator = new OrderAggregator(services);
const dashboard = await aggregator.getDashboard(req.user.id);
res.json(dashboard);
} catch (error) {
next(error);
}
});
Backend for Frontend (BFF) Pattern
Different clients need different APIs.Copy
┌─────────────────────────────────────────────────────────────────────────────┐
│ BFF PATTERN │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Mobile │ │ Web │ │ Admin │ │
│ │ App │ │ App │ │ Portal │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Mobile │ │ Web │ │ Admin │ │
│ │ BFF │ │ BFF │ │ BFF │ │
│ │ ──────── │ │ ──────── │ │ ──────── │ │
│ │ • Slim │ │ • Rich │ │ • Full │ │
│ │ • Cached │ │ • Fast │ │ • Detailed│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └───────────────┼───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ MICROSERVICES │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │User │ │Order│ │Prod │ │Ship │ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mobile BFF Implementation
Copy
// mobile-bff/src/routes/products.js
const express = require('express');
const router = express.Router();
// Mobile-optimized product list
router.get('/', async (req, res) => {
const products = await productService.getProducts(req.query);
// Return slim response for mobile
res.json({
products: products.map(p => ({
id: p.id,
name: p.name,
price: p.price,
thumbnail: p.images[0]?.thumbnail, // Only first thumbnail
rating: p.averageRating,
inStock: p.stockQuantity > 0
})),
pagination: products.pagination
});
});
// Mobile-optimized product details
router.get('/:id', async (req, res) => {
const [product, reviews] = await Promise.all([
productService.getProduct(req.params.id),
reviewService.getTopReviews(req.params.id, 3) // Only top 3 reviews
]);
res.json({
id: product.id,
name: product.name,
price: product.price,
description: product.shortDescription, // Short description
images: product.images.slice(0, 5), // Max 5 images
rating: product.averageRating,
reviewCount: product.reviewCount,
topReviews: reviews,
inStock: product.stockQuantity > 0,
variants: product.variants.map(v => ({
id: v.id,
name: v.name,
price: v.price,
inStock: v.stockQuantity > 0
}))
});
});
Web BFF Implementation
Copy
// web-bff/src/routes/products.js
const express = require('express');
const router = express.Router();
// Web-optimized product list
router.get('/', async (req, res) => {
const [products, categories, filters] = await Promise.all([
productService.getProducts(req.query),
categoryService.getCategories(),
productService.getAvailableFilters(req.query)
]);
// Rich response for web
res.json({
products: products.map(p => ({
id: p.id,
name: p.name,
description: p.shortDescription,
price: p.price,
originalPrice: p.originalPrice,
discount: p.discount,
images: p.images,
rating: p.averageRating,
reviewCount: p.reviewCount,
inStock: p.stockQuantity > 0,
badges: p.badges,
categories: p.categories
})),
pagination: products.pagination,
categories,
filters,
meta: {
totalProducts: products.total,
executionTime: Date.now() - req.startTime
}
});
});
// Web-optimized product details
router.get('/:id', async (req, res) => {
const [product, reviews, related, questions] = await Promise.all([
productService.getProduct(req.params.id),
reviewService.getReviews(req.params.id, { limit: 10 }),
productService.getRelatedProducts(req.params.id, 8),
questionService.getQuestions(req.params.id, { limit: 5 })
]);
res.json({
product: {
...product,
specifications: product.specifications,
fullDescription: product.fullDescription
},
reviews: {
items: reviews.items,
summary: reviews.summary,
pagination: reviews.pagination
},
relatedProducts: related,
questions: questions,
breadcrumbs: await categoryService.getBreadcrumbs(product.categoryId)
});
});
Circuit Breaker in Gateway
Copy
// gateway/src/middleware/circuitBreaker.js
const CircuitBreaker = require('opossum');
class GatewayCircuitBreaker {
constructor() {
this.breakers = new Map();
}
getBreaker(serviceName, options = {}) {
if (this.breakers.has(serviceName)) {
return this.breakers.get(serviceName);
}
const breaker = new CircuitBreaker(
async (config) => {
const response = await axios(config);
return response;
},
{
timeout: options.timeout || 10000,
errorThresholdPercentage: options.errorThreshold || 50,
resetTimeout: options.resetTimeout || 30000,
volumeThreshold: options.volumeThreshold || 10,
name: serviceName
}
);
// Events
breaker.on('open', () => {
console.log(`Circuit OPEN for ${serviceName}`);
this.alertService?.notify({
type: 'circuit_open',
service: serviceName,
timestamp: new Date()
});
});
breaker.on('halfOpen', () => {
console.log(`Circuit HALF-OPEN for ${serviceName}`);
});
breaker.on('close', () => {
console.log(`Circuit CLOSED for ${serviceName}`);
});
breaker.on('fallback', (result) => {
console.log(`Fallback triggered for ${serviceName}`);
});
this.breakers.set(serviceName, breaker);
return breaker;
}
async call(serviceName, config, fallback = null) {
const breaker = this.getBreaker(serviceName);
if (fallback) {
breaker.fallback(fallback);
}
try {
return await breaker.fire(config);
} catch (error) {
if (error.message.includes('Breaker is open')) {
throw new ServiceUnavailableError(serviceName);
}
throw error;
}
}
getStatus() {
const status = {};
for (const [name, breaker] of this.breakers) {
status[name] = {
state: breaker.opened ? 'OPEN' : breaker.halfOpen ? 'HALF_OPEN' : 'CLOSED',
stats: breaker.stats
};
}
return status;
}
}
// Usage in route
router.get('/api/users/:id', async (req, res, next) => {
try {
const response = await circuitBreaker.call(
'user-service',
{
method: 'get',
url: `${services.users}/users/${req.params.id}`
},
// Fallback
() => ({
data: {
id: req.params.id,
name: 'Unknown User',
_fallback: true
}
})
);
res.json(response.data);
} catch (error) {
next(error);
}
});
// Circuit breaker status endpoint
router.get('/health/circuits', (req, res) => {
res.json(circuitBreaker.getStatus());
});
Gateway with Kong
Kong is a popular open-source API Gateway.Copy
# kong.yml - Declarative Configuration
_format_version: "3.0"
services:
- name: user-service
url: http://user-service:3001
routes:
- name: user-routes
paths:
- /api/users
strip_path: false
plugins:
- name: rate-limiting
config:
minute: 100
policy: redis
redis_host: redis
- name: jwt
config:
secret_is_base64: false
- name: cors
config:
origins:
- "*"
methods:
- GET
- POST
- PUT
- DELETE
- name: order-service
url: http://order-service:3002
routes:
- name: order-routes
paths:
- /api/orders
plugins:
- name: rate-limiting
config:
minute: 50
- name: request-transformer
config:
add:
headers:
- X-Gateway-Source:kong
- name: product-service
url: http://product-service:3003
routes:
- name: product-routes
paths:
- /api/products
plugins:
- name: proxy-cache
config:
strategy: memory
content_type:
- application/json
cache_ttl: 300
# Global plugins
plugins:
- name: correlation-id
config:
header_name: X-Correlation-ID
generator: uuid
- name: prometheus
config:
per_consumer: true
- name: request-termination
route: null
service: null
enabled: false # Enable when needed
config:
status_code: 503
message: "Service temporarily unavailable"
Copy
# docker-compose.yml for Kong
version: '3.8'
services:
kong-database:
image: postgres:13
environment:
POSTGRES_USER: kong
POSTGRES_DB: kong
POSTGRES_PASSWORD: kongpass
volumes:
- kong-data:/var/lib/postgresql/data
kong-migration:
image: kong:3.4
command: kong migrations bootstrap
depends_on:
- kong-database
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_PASSWORD: kongpass
kong:
image: kong:3.4
depends_on:
- kong-database
- kong-migration
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_PASSWORD: kongpass
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: 0.0.0.0:8001
ports:
- "8000:8000" # Proxy
- "8001:8001" # Admin API
- "8443:8443" # Proxy SSL
- "8444:8444" # Admin SSL
volumes:
kong-data:
Interview Questions
Q1: What are the key responsibilities of an API Gateway?
Q1: What are the key responsibilities of an API Gateway?
Answer:
- Request Routing: Route requests to appropriate services
- Authentication/Authorization: Validate tokens, check permissions
- Rate Limiting: Protect services from overload
- Load Balancing: Distribute traffic across service instances
- Request/Response Transformation: Modify headers, body
- Caching: Cache responses for performance
- Monitoring/Logging: Centralized observability
- Circuit Breaking: Prevent cascade failures
- Protocol Translation: REST to gRPC, etc.
- Request Aggregation: Combine multiple service calls
Q2: What is the BFF (Backend for Frontend) pattern?
Q2: What is the BFF (Backend for Frontend) pattern?
Answer:BFF creates separate API gateways for different client types:Why:
- Mobile needs slim responses (bandwidth)
- Web needs rich responses (features)
- Admin needs full data access
- Optimized responses per client
- Independent evolution
- Better team ownership
- Client-specific concerns
- More services to maintain
- Potential code duplication
- Need for shared libraries
Q3: How do you handle authentication in an API Gateway?
Q3: How do you handle authentication in an API Gateway?
Answer:Common patterns:
- JWT Validation: Gateway validates token, forwards user info in headers
- OAuth 2.0: Gateway handles token introspection
- API Keys: Gateway validates keys, applies rate limits per key
- Session-based: Gateway manages sessions
- Validate token at gateway
- Extract user claims
- Forward as headers to services (X-User-ID, X-User-Roles)
- Services trust gateway (internal network)
- Use mutual TLS between gateway and services
Q4: How do you prevent the API Gateway from becoming a bottleneck?
Q4: How do you prevent the API Gateway from becoming a bottleneck?
Answer:Strategies:
- Horizontal Scaling: Multiple gateway instances behind load balancer
- Stateless Design: No session state in gateway
- Efficient Routing: Use performant routing algorithms
- Caching: Cache static/semi-static responses
- Async Processing: Non-blocking I/O
- Connection Pooling: Reuse connections to services
- Track latency added by gateway
- Monitor CPU/memory usage
- Set up auto-scaling based on metrics
- Service mesh for internal traffic
- Edge computing for some operations
Summary
Key Takeaways
- API Gateway is the single entry point
- Handles cross-cutting concerns centrally
- BFF pattern optimizes for different clients
- Circuit breakers prevent cascade failures
- Kong/AWS API Gateway for production
Next Steps
In the next chapter, we’ll dive into Data Management patterns including database per service, sagas, and event sourcing.