API Gateway Implementation
Learn API Gateway patterns for microservices including routing, load balancing, authentication, and rate limiting. This is a foundational concept in server-side JavaScript development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world Node.js experience. Take your time with each section and practice the examples
API Gateway Fundamentals
An API Gateway is a single entry point for all client requests to microservices, providing routing, load balancing, authentication, and other cross-cutting concerns.. This is an essential concept that every Node.js developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Basic API Gateway
// Express-based API Gateway
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const app = express();
// security middleware
app.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);
// Service registry
const services = {
user: {
url: process.env.USER_SERVICE_URL || 'http://localhost:3001',
health: '/health'
},
product: {
url: process.env.PRODUCT_SERVICE_URL || 'http://localhost:3002',
health: '/health'
},
order: {
url: process.env.ORDER_SERVICE_URL || 'http://localhost:3003',
health: '/health'
}
};
// Health check for all services
app.get('/health', async (req, res) => {
const healthStatus = {};
for (const [serviceName, service] of Object.entries(services)) {
try {
const response = await fetch(`${service.url}${service.health}`);
healthStatus[serviceName] = {
status: response.ok ? 'healthy' : 'unhealthy',
responseTime: Date.now() - startTime
};
} catch (error) {
healthStatus[serviceName] = {
status: 'unhealthy',
error: error.message
};
}
}
res.json(healthStatus);
});Authentication & Authorization
// JWT-based authentication middleware
const jwt = require('jsonwebtoken');
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user;
next();
});
};
// Role-based authorization
const authorize = (roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
// Usage
app.use('/api/users', authenticateToken);
app.use('/api/admin', authenticateToken, authorize(['admin']));Request Routing & Load Balancing
// Advanced routing with load balancing
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
class LoadBalancer {
constructor(services) {
this.services = services;
this.currentIndex = 0;
}
getNextService() {
const service = this.services[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.services.length;
return service;
}
getHealthyServices() {
return this.services.filter(service => service.healthy);
}
}
// Service discovery and health checking
class ServiceRegistry {
constructor() {
this.services = new Map();
}
register(serviceName, serviceUrl) {
this.services.set(serviceName, {
url: serviceUrl,
healthy: true,
lastCheck: Date.now()
});
}
async checkHealth(serviceName) {
const service = this.services.get(serviceName);
if (!service) return false;
try {
const response = await fetch(`${service.url}/health`);
service.healthy = response.ok;
service.lastCheck = Date.now();
return service.healthy;
} catch (error) {
service.healthy = false;
service.lastCheck = Date.now();
return false;
}
}
getHealthyServices(serviceName) {
const service = this.services.get(serviceName);
return service && service.healthy ? [service] : [];
}
}
// API Gateway with load balancing
const registry = new ServiceRegistry();
registry.register('user', 'http://user-service-1:3001');
registry.register('user', 'http://user-service-2:3001');
app.use('/api/users', (req, res, next) => {
const healthyServices = registry.getHealthyServices('user');
if (healthyServices.length === 0) {
return res.status(503).json({ error: 'Service unavailable' });
}
const loadBalancer = new LoadBalancer(healthyServices);
const targetService = loadBalancer.getNextService();
const proxy = createProxyMiddleware({
target: targetService.url,
changeOrigin: true,
onError: (err, req, res) => {
res.status(503).json({ error: 'Service unavailable' });
}
});
proxy(req, res, next);
});Rate Limiting & Throttling
// Advanced rate limiting
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
// Different rate limits for different endpoints
const createRateLimit = (windowMs, max, message) => {
return rateLimit({
store: new RedisStore({
sendCommand: (...args) => redis.call(...args),
}),
windowMs,
max,
message: { error: message },
standardHeaders: true,
legacyHeaders: false,
});
};
// Global rate limit
app.use(createRateLimit(15 * 60 * 1000, 100, 'Too many requests from this IP'));
// User-specific rate limit
app.use('/api/users', createRateLimit(15 * 60 * 1000, 50, 'Too many user requests'));
// Admin rate limit
app.use('/api/admin', createRateLimit(15 * 60 * 1000, 200, 'Too many admin requests'));Mini-Project: Complete API Gateway
// Complete API Gateway implementation
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const jwt = require('jsonwebtoken');
const Redis = require('ioredis');
const app = express();
const redis = new Redis(process.env.REDIS_URL);
// Middleware
app.use(helmet());
app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
});
app.use(limiter);
// Service registry
class ServiceRegistry {
constructor() {
this.services = new Map();
}
register(name, url, healthEndpoint = '/health') {
if (!this.services.has(name)) {
this.services.set(name, []);
}
this.services.get(name).push({ url, healthEndpoint, healthy: true });
}
async checkHealth() {
for (const [serviceName, instances] of this.services) {
for (const instance of instances) {
try {
const response = await fetch(`${instance.url}${instance.healthEndpoint}`);
instance.healthy = response.ok;
} catch (error) {
instance.healthy = false;
}
}
}
}
getHealthyInstances(serviceName) {
const instances = this.services.get(serviceName) || [];
return instances.filter(instance => instance.healthy);
}
}
const registry = new ServiceRegistry();
// Register services
registry.register('user', 'http://user-service:3001');
registry.register('product', 'http://product-service:3002');
registry.register('order', 'http://order-service:3003');
// Health check every 30 seconds
setInterval(() => {
registry.checkHealth();
}, 30000);
// Authentication middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user;
next();
});
};
// Load balancer
class LoadBalancer {
constructor(instances) {
this.instances = instances;
this.currentIndex = 0;
}
getNextInstance() {
if (this.instances.length === 0) {
throw new Error('No healthy instances available');
}
const instance = this.instances[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.instances.length;
return instance;
}
}
// Proxy middleware factory
const createProxy = (serviceName) => {
return (req, res, next) => {
const healthyInstances = registry.getHealthyInstances(serviceName);
if (healthyInstances.length === 0) {
return res.status(503).json({
error: 'Service unavailable',
service: serviceName
});
}
const loadBalancer = new LoadBalancer(healthyInstances);
const targetInstance = loadBalancer.getNextInstance();
const proxy = createProxyMiddleware({
target: targetInstance.url,
changeOrigin: true,
onError: (err, req, res) => {
res.status(503).json({
error: 'Service unavailable',
service: serviceName
});
}
});
proxy(req, res, next);
};
};
// Routes
app.get('/health', (req, res) => {
const healthStatus = {};
for (const [serviceName, instances] of registry.services) {
healthStatus[serviceName] = instances.map(instance => ({
url: instance.url,
healthy: instance.healthy
}));
}
res.json(healthStatus);
});
// Service routes
app.use('/api/users', authenticateToken, createProxy('user'));
app.use('/api/products', createProxy('product'));
app.use('/api/orders', authenticateToken, createProxy('order'));
// Error handling
app.use((err, req, res, next) => {
console.error('Gateway error:', err);
res.status(500).json({ error: 'Internal gateway error' });
});
app.listen(3000, () => {
console.log('API Gateway running on port 3000');
});