Build an API Gateway
Introduction
An API gateway is a server that acts as a single entry point for a set of microservices. It handles request routing, composition, authentication, and rate limiting.
In this tutorial, we'll build "APIGateway" - a complete API gateway with routing, authentication, rate limiting, and request transformation.
What You'll Build
- Central API gateway server
- Dynamic request routing
- JWT authentication
- Rate limiting per client
- Request/response transformation
Core Concepts
Understanding API gateway architecture.
Gateway Functions
- Routing - Direct requests to services
- Aggregation - Combine multiple requests
- Authentication - Verify credentials
- Rate Limiting - Control API usage
Project Setup
Bash
Bash
mkdir api-gateway
cd api-gateway
npm init -y
npm install express http-proxy-middleware jwt cors helmet
Gateway Core
JavaScript
// gateway.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
class APIGateway {
constructor() {
this.app = express();
this.routes = new Map();
this.middleware = [];
this.setupMiddleware();
}
setupMiddleware() {
this.app.use(helmet());
this.app.use(cors());
this.app.use(express.json());
}
addRoute(path, service, auth = false) {
this.routes.set(path, { service, auth });
}
registerMiddleware(fn) {
this.middleware.push(fn);
}
handleRequest(req, res) {
let matchedRoute = null;
for (const [path, route] of this.routes.entries()) {
if req.path.startsWith(path) {
matchedRoute = { path, ...route };
break;
}
}
if (!matchedRoute) {
return res.status(404).json({ error: 'Route not found' });
}
const targetPath = req.path.replace(matchedRoute.path, '');
req.url = targetPath;
req.headers['x-gateway-route'] = matchedRoute.service;
this.proxyRequest(req, res, matchedRoute.service);
}
proxyRequest(req, res, service) {
const services = {
users: 'http://localhost:3001',
products: 'http://localhost:3002',
orders: 'http://localhost:3003'
};
const target = services[service];
if (!target) {
return res.status(502).json({ error: 'Service unavailable' });
}
// Simple proxy implementation
const http = require('http');
const proxyReq = http.request({
hostname: target.replace('http://', '').split(':')[0],
port: parseInt(target.split(':')[2]) || 80,
path: req.url,
method: req.method,
headers: req.headers
}, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
req.pipe(proxyReq);
}
start(port) {
for (const fn of this.middleware) {
this.app.use(fn);
}
this.app.all('*', (req, res) => this.handleRequest(req, res));
this.app.listen(port, () => {
console.log(`API Gateway running on port ${port}`);
});
}
}
const gateway = new APIGateway();
gateway.addRoute('/api/users', 'users', true);
gateway.addRoute('/api/products', 'products', true);
gateway.addRoute('/api/orders', 'orders', true);
gateway.start(8080);
Request Routing
JavaScript
// router.js
class RouteManager {
constructor() {
this.routes = [];
this.wildcardRoutes = [];
}
addRoute(config) {
if config.path.includes('*')) {
this.wildcardRoutes.push(this.compileRoute(config));
} else {
this.routes.push(this.compileRoute(config));
}
}
compileRoute(config) {
return {
...config,
regex: new RegExp(`^${config.path.replace(/:(\w+), '(?<$1>[^/]+)')}$`),
params: (config.path.match(/:(\w+)/g) || []).map(p => p.slice(1))
};
}
match(method, path) {
for (const route of this.routes) {
if route.method === method && route.regex.test(path)) {
const match = route.regex.exec(path);
return { route, params: match.groups || {} };
}
}
return null;
}
transformRequest(req, route) {
if (route.requestTransform) {
req.body = route.requestTransform(req.body);
}
if (route.addHeaders) {
Object.assign(req.headers, route.addHeaders);
}
}
transformResponse(res, route, data) {
if (route.responseTransform) {
return route.responseTransform(data);
}
return data;
}
}
Authentication
JavaScript
// auth.js
const jwt = require('jsonwebtoken');
class AuthMiddleware {
constructor(secret) {
this.secret = secret;
}
verifyToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, this.secret);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
}
requireRole(...roles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions'});
}
next();
};
}
}
Rate Limiting
JavaScript
// rateLimiter.js
class RateLimiter {
constructor(options = {}) {
this.windowMs = options.windowMs || 60000;
this.maxRequests = options.maxRequests || 100;
this.store = new Map();
}
getKey(req) {
return req.user?.id || req.ip || 'anonymous';
}
isAllowed(key) {
const now = Date.now();
const windowStart = now - this.windowMs;
if (!this.store.has(key)) {
this.store.set(key, []);
}
const requests = this.store.get(key).filter(t => t > windowStart);
if (requests.length >= this.maxRequests) {
return { allowed: false, remaining: 0 };
}
requests.push(now);
this.store.set(key, requests);
return {
allowed: true,
remaining: this.maxRequests - requests.length
};
}
middleware() {
return (req, res, next) => {
const key = this.getKey(req);
const { allowed, remaining } = this.isAllowed(key);
res.set('X-RateLimit-Limit', this.maxRequests);
res.set('X-RateLimit-Remaining', remaining);
if (!allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
next();
};
}
}
Testing
Bash
# Test the API Gateway
# Access routes
curl http://localhost:8080/api/users
curl http://localhost:8080/api/products
# With authentication
TOKEN=$(curl -s -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password"}' | jq -r .token)
curl http://localhost:8080/api/users \
-H "Authorization: Bearer $TOKEN"
Testing Checklist
- Routes are correctly routed to services
- Authentication is enforced
- Rate limiting works
- Response transformation works
Summary
You've built a complete API gateway.
What You Built
- Gateway Core - Central entry point
- Dynamic Routing - Service routing
- Authentication - JWT verification
- Rate Limiting - Per-client limits