API Gateway Patterns
What an API Gateway Does
Client Request
|
v
API Gateway
| | | |
| | | +-- Rate Limiting
| | +------ Authentication/Authorization
| +---------- Request/Response Transform
+-------------- Routing, Load Balancing, Circuit Breaking
|
v
Microservices (User Service, Order Service, Product Service...)
Backend for Frontend (BFF) Pattern
// Web BFF: Aggregates data optimized for desktop web
app.get('/dashboard', authenticate, async (req, res) => {
const userId = req.user.id;
// Aggregate from multiple services in parallel
const [user, orders, recommendations, notifications] = await Promise.all([
userService.getProfile(userId),
orderService.getRecentOrders(userId, { limit: 5 }),
recommendationService.getForUser(userId, { limit: 6 }),
notificationService.getUnread(userId),
]);
// Shape data for web UI needs
res.json({
user: { name: user.name, avatar: user.avatar, plan: user.plan },
recentOrders: orders.map(o => ({ id: o.id, status: o.status, total: o.total, date: o.createdAt })),
recommendations: recommendations.map(r => ({ id: r.id, name: r.name, price: r.price, image: r.imageUrl })),
unreadCount: notifications.filter(n => !n.read).length,
});
});
// Mobile BFF: Less data, mobile-optimized fields
mobileBFF.get('/dashboard', authenticate, async (req, res) => {
const [user, notifications] = await Promise.all([
userService.getProfile(req.user.id),
notificationService.getUnread(req.user.id),
]);
// Mobile needs less data (bandwidth conscious)
res.json({
name: user.name,
avatar: user.avatarSmall, // Smaller image
unreadCount: notifications.length,
});
});
Request Aggregation
// Aggregate multiple service calls into one API call
app.get('/products/:id/full', async (req, res) => {
const productId = req.params.id;
const [product, inventory, reviews, seller] = await Promise.allSettled([
productService.getProduct(productId),
inventoryService.getStock(productId),
reviewService.getSummary(productId),
sellerService.getSeller(productId),
]);
// Handle partial failures gracefully
res.json({
product: product.status === 'fulfilled' ? product.value : null,
inStock: inventory.status === 'fulfilled' ? inventory.value.quantity > 0 : null,
rating: reviews.status === 'fulfilled' ? reviews.value.average : null,
seller: seller.status === 'fulfilled' ? seller.value : null,
});
});
Cross-Cutting Concerns
// Authentication middleware
const authenticate = async (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Authentication required' });
try {
req.user = await verifyJWT(token);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
};
// Request ID for tracing
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] as string || uuid();
res.setHeader('x-request-id', req.id);
next();
});
// Timeout middleware
app.use((req, res, next) => {
const timeout = setTimeout(() => {
res.status(504).json({ error: 'Gateway Timeout' });
}, 30000);
res.on('finish', () => clearTimeout(timeout));
next();
});
// Circuit breaker per downstream service
const orderCircuit = new CircuitBreaker({ failureThreshold: 5, timeout: 60000 });
app.get('/orders', authenticate, async (req, res) => {
try {
const orders = await orderCircuit.execute(() => orderService.list(req.user.id));
res.json(orders);
} catch (err) {
if (err.message === 'Circuit breaker is open') {
return res.status(503).json({ error: 'Service temporarily unavailable' });
}
throw err;
}
});
Protocol Translation
// gRPC-to-REST gateway
import grpc from '@grpc/grpc-js';
const userServiceClient = new UserServiceClient('user-service:50051', grpc.credentials.createInsecure());
app.get('/users/:id', async (req, res) => {
// REST request -> gRPC call -> REST response
userServiceClient.getUser({ id: req.params.id }, (err, user) => {
if (err) return res.status(err.code === 5 ? 404 : 500).json({ error: err.message });
res.json({ id: user.id, name: user.name, email: user.email });
});
});
The API Gateway is the front door to your microservices - keep it thin and focused on routing concerns.