API Gateway — Kong, APISIX, Custom Gateway Với Node.js
Thiết kế và triển khai API Gateway: Kong, APISIX, custom gateway — routing, rate limiting, auth, transformation.
API Gateway là entry point duy nhất cho tất cả client — routing, auth, rate limiting, transformation tập trung tại một chỗ.
Gateway Làm Gì?#
Client → API Gateway
├── /api/users → users-service
├── /api/orders → orders-service
├── /api/products → products-service
└── /graphql → graphql-servicetxtChức năng:
- Routing — chuyển request đến service đúng
- Authentication — xác thực trước khi vào internal
- Rate limiting — bảo vệ backend
- Transformation — đổi format request/response
- Caching — cache response cho GET request
- Monitoring — collect metrics và logs
- Load balancing — phân phối đến nhiều instances
Kong Gateway#
Kong là open-source API gateway phổ biến nhất — dùng Nginx core.
Docker Compose#
services:
kong:
image: kong:latest
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
ports:
- "8000:8000" # Proxy
- "8001:8001" # Admin API
- "8443:8443" # Proxy HTTPS
depends_on:
- kong-database
kong-database:
image: postgres:16-alpine
environment:
POSTGRES_USER: kong
POSTGRES_DB: kongyamlConfig Routes#
# Add service
curl -X POST http://localhost:8001/services \
--data name=users-service \
--data url=http://users-container:3000
# Add route
curl -X POST http://localhost:8001/services/users-service/routes \
--data paths[]=/api/users \
--data methods[]=GET \
--data methods[]=POST
# Enable rate limiting plugin
curl -X POST http://localhost:8001/services/users-service/plugins \
--data name=rate-limiting \
--data config.minute=100 \
--data config.policy=local
# Enable auth plugin (JWT)
curl -X POST http://localhost:8001/services/users-service/plugins \
--data name=jwtbashAPISIX — Modern, Fast#
APISIX dùng Lua (OpenResty), nhanh hơn Kong ở nhiều benchmark.
services:
apisix:
image: apache/apisix:latest
ports:
- "9080:9080" # HTTP
- "9443:9443" # HTTPS
volumes:
- ./apisix.yaml:/usr/local/apisix/conf/config.yamlyaml# apisix.yaml
routes:
- uri: /api/users/*
upstream:
nodes:
"users-service:3000": 1
- uri: /api/orders/*
upstream:
nodes:
"orders-service:3000": 1
plugins:
jwt-auth: ~
limit-count:
count: 100
time_window: 60
prometheus: ~yamlCustom Gateway — Node.js + Express#
Khi công cụ có sẵn không đáp ứng — tự viết gateway:
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
import rateLimit from 'express-rate-limit';
import Redis from 'ioredis';
const app = express();
const redis = new Redis();
// Global middleware
app.use(helmet());
app.use(cors({ origin: ['https://myapp.com'] }));
// Auth middleware
async function authenticate(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split('Bearer ')[1];
if (!token) {
return res.status(401).json({ error: 'Missing token' });
}
// Verify với auth service
const user = await verifyToken(token);
if (!user) {
return res.status(401).json({ error: 'Invalid token' });
}
req.user = user;
next();
}
// Rate limit dựa trên API key
app.use('/api', rateLimit({
keyGenerator: (req) => req.headers['x-api-key'] as string || req.ip,
windowMs: 60_000,
max: 100,
}));
// Route to services
const services = {
users: 'http://users-service:3000',
orders: 'http://orders-service:3000',
products: 'http://products-service:3000',
notifications: 'http://notifications-service:3000',
};
Object.entries(services).forEach(([name, target]) => {
app.use(`/api/${name}`, authenticate, createProxyMiddleware({
target,
changeOrigin: true,
onProxyReq: (proxyReq, req) => {
// Forward user info
proxyReq.setHeader('X-User-Id', req.user.id);
proxyReq.setHeader('X-User-Role', req.user.role);
},
onError: (err, req, res) => {
logger.error({ err, service: name }, 'Proxy error');
res.status(502).json({ error: `${name} service unavailable` });
},
}));
});
// Circuit breaker pattern
class CircuitBreakerProxy {
private failures = 0;
private state: 'closed' | 'open' = 'closed';
async proxy(req: Request, res: Response, target: string) {
if (this.state === 'open') {
return res.status(503).json({ error: 'Service circuit open' });
}
try {
await proxyRequest(req, res, target);
this.failures = 0;
} catch {
this.failures++;
if (this.failures >= 5) {
this.state = 'open';
setTimeout(() => { this.state = 'closed'; this.failures = 0; }, 30000);
}
throw;
}
}
}typescriptResponse Aggregation#
Gateway gộp response từ nhiều service:
app.get('/api/dashboard', authenticate, async (req, res) => {
try {
const [user, orders, notifications] = await Promise.all([
fetch(`http://users-service:3000/users/${req.user.id}`).then(r => r.json()),
fetch(`http://orders-service:3000/orders?userId=${req.user.id}&limit=5`).then(r => r.json()),
fetch(`http://notifications-service:3000/notifications?userId=${req.user.id}`).then(r => r.json()),
]);
res.json({
user: { id: user.id, name: user.name, email: user.email },
recentOrders: orders,
unreadNotifications: notifications.filter(n => !n.read).length,
});
} catch (err) {
// Partial failure — trả về những gì có
res.json({
user: user || null,
recentOrders: orders || [],
unreadNotifications: 0,
_degraded: true,
});
}
});typescriptGateway với GraphQL Federation#
Gateway GraphQL hợp nhất nhiều GraphQL service:
import { ApolloGateway, IntrospectAndCompose } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://users-service:4001/graphql' },
{ name: 'orders', url: 'http://orders-service:4002/graphql' },
{ name: 'reviews', url: 'http://reviews-service:4003/graphql' },
],
}),
});
const server = new ApolloServer({ gateway });
await server.listen({ port: 4000 });typescriptCaching Trong Gateway#
// Cache response trong Redis
app.get('/api/products/*', async (req, res) => {
const cacheKey = `gateway:${req.originalUrl}`;
const cached = await redis.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
// Forward to service
const response = await fetch(`http://products-service:3000${req.path}`);
const data = await response.json();
// Cache 5 phút
await redis.setex(cacheKey, 300, JSON.stringify(data));
res.json(data);
});typescriptMonitoring#
Gateway là nơi lý tưởng để collect metrics:
const requestCounter = new prometheus.Counter({
name: 'gateway_requests_total',
help: 'Total requests',
labelNames: ['service', 'method', 'status'],
});
const requestDuration = new prometheus.Histogram({
name: 'gateway_request_duration_seconds',
help: 'Request duration',
labelNames: ['service'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2],
});
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const service = req.path.split('/')[2] || 'unknown';
requestCounter.inc({ service, method: req.method, status: res.statusCode });
requestDuration.observe({ service }, (Date.now() - start) / 1000);
});
next();
});typescriptKết Luận#
| Giải pháp | Khi nào dùng |
|---|---|
| Kong | Cần mature, nhiều plugin |
| APISIX | Cần performance cao |
| Custom Node.js | Cần logic phức tạp |
| GraphQL Federation | GraphQL microservices |
| Cloudflare API Gateway | Edge gateway |
Gateway không phải “nice to have” — nó essential cho microservices. Tập trung auth, rate limiting, routing tại gateway giúp service đơn giản hơn, tập trung vào business logic.