Kong API Gateway — Complete Guide
In this tutorial, you'll learn about Kong API Gateway. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Kong is an open-source API gateway built on NGINX, using a plugin architecture for authentication, Rate Limiting, logging, and request/response transformation.
What You'll Learn
By the end of this lesson, you will configure Kong services and routes, enable plugins for auth and rate limiting, and understand the Kong plugin ecosystem.
Why It Matters
Kong is one of the most popular open-source gateways, used by companies like Yelp, WeWork, and Grab. Its plugin architecture makes it extensible without modifying backend code.
Real-World Use
A microservice platform registers each service in Kong, adds JWT authentication and rate limiting via plugins, and uses Kong's admin API for dynamic configuration.
Kong Architecture
flowchart LR
Client -->|Port 8000| Kong[Kong Gateway]
Kong -->|Routes| S1[Service 1]
Kong -->|Routes| S2[Service 2]
Admin[Admin API - Port 8001] --> Kong
Kong -->|Plugins| Plugin[Plugin: Auth/Rate Limit/Log]
style Kong fill:#22c55e,color:#fff
Kong Service and Route Configuration
# kong_config.py
import json
from typing import Dict, List, Optional
class KongConfigurator:
def __init__(self, admin_url: str = "http://localhost:8001"):
self.admin_url = admin_url
self.services: Dict[str, dict] = {}
self.routes: Dict[str, list] = {}
self.plugins: Dict[str, list] = {}
def add_service(self, name: str, url: str, protocol: str = "http",
retries: int = 5, connect_timeout: int = 60000):
self.services[name] = {
"name": name,
"url": url,
"protocol": protocol,
"retries": retries,
"connect_timeout": connect_timeout,
"write_timeout": 60000,
"read_timeout": 60000,
}
self.routes[name] = []
self.plugins[name] = []
def add_route(self, service: str, paths: List[str],
methods: Optional[List[str]] = None,
hosts: Optional[List[str]] = None,
strip_path: bool = True):
if service not in self.services:
return
self.routes[service].append({
"service": service,
"paths": paths,
"methods": methods,
"hosts": hosts,
"strip_path": strip_path,
})
def add_plugin(self, service: str, plugin_name: str, config: dict):
if service not in self.plugins:
return
self.plugins[service].append({
"name": plugin_name,
"service": service,
"config": config,
})
def export(self) -> dict:
return {
"services": list(self.services.values()),
"routes": {s: self.routes[s] for s in self.services},
"plugins": {s: self.plugins[s] for s in self.services},
}
kong = KongConfigurator()
kong.add_service("users", "http://user-service:3000")
kong.add_service("orders", "http://order-service:4000")
kong.add_service("products", "http://product-service:5000")
kong.add_route("users", ["/api/users", "/api/users/:id"],
methods=["GET", "POST", "PUT", "DELETE"])
kong.add_route("orders", ["/api/orders"],
methods=["GET", "POST"])
kong.add_route("products", ["/api/products"],
methods=["GET"], strip_path=False)
kong.add_plugin("users", "rate-limiting", {
"minute": 100,
"policy": "local",
})
kong.add_plugin("users", "jwt", {})
kong.add_plugin("orders", "rate-limiting", {
"minute": 50,
"policy": "local",
})
config = kong.export()
print(f"Services: {len(config['services'])}")
print(f"Routes: {sum(len(r) for r in config['routes'].values())}")
print(f"Plugins: {sum(len(p) for p in config['plugins'].values())}")
for svc in config['services']:
print(f"\n Service: {svc['name']} -> {svc['url']}")
for route in config['routes'][svc['name']]:
print(f" Route: {route['paths']}")
for plugin in config['plugins'][svc['name']]:
print(f" Plugin: {plugin['name']}")
Expected output:
Services: 3
Routes: 3
Plugins: 3
Service: users -> http://user-service:3000
Route: ['/api/users', '/api/users/:id']
Plugin: rate-limiting
Plugin: jwt
Service: orders -> http://order-service:4000
Route: ['/api/orders']
Plugin: rate-limiting
Service: products -> http://product-service:5000
Route: ['/api/products']
Kong Authentication Plugin
# kong_auth.py
import time
from typing import Dict, List, Optional
class KongAuthPlugin:
def __init__(self, plugin_name: str = "key-auth"):
self.plugin_name = plugin_name
self.consumers: Dict[str, dict] = {}
self.credentials: Dict[str, str] = {}
def add_consumer(self, consumer_id: str, username: str,
custom_id: Optional[str] = None):
self.consumers[consumer_id] = {
"username": username,
"custom_id": custom_id or consumer_id,
"created_at": time.time(),
}
def add_key_auth(self, consumer_id: str, key: str):
self.credentials[key] = consumer_id
def authenticate(self, headers: Dict) -> Optional[dict]:
api_key = headers.get("apikey") or headers.get("X-Api-Key")
if not api_key:
return None
consumer_id = self.credentials.get(api_key)
if not consumer_id:
return None
consumer = self.consumers.get(consumer_id)
if not consumer:
return None
return {
"authenticated": True,
"consumer": consumer["username"],
"consumer_id": consumer_id,
}
auth = KongAuthPlugin()
auth.add_consumer("c_1", "acme-corp")
auth.add_consumer("c_2", "startup-inc")
auth.add_key_auth("c_1", "key_acme_123")
auth.add_key_auth("c_2", "key_startup_456")
tests = [
{"apikey": "key_acme_123"},
{"X-Api-Key": "key_startup_456"},
{"X-Api-Key": "invalid_key"},
{},
]
for headers in tests:
result = auth.authenticate(headers)
if result:
print(f"Authed: consumer={result['consumer']}, id={result['consumer_id']}")
else:
print("Auth failed")
Expected output:
Authed: consumer=acme-corp, id=c_1
Authed: consumer=startup-inc, id=c_2
Auth failed
Auth failed
Common Mistakes
1. Using in-memory Database in Production
Kong's default in-memory DB (DB-less) is for development. Use PostgreSQL or Cassandra for production.
2. Not Enabling Health Checks
Without health checks, Kong routes to unhealthy upstream services. Configure active or passive health checks.
3. Overloading with Too Many Plugins
Each plugin adds latency. Disable unused plugins and prefer built-in plugins over custom Lua plugins.
4. Forgetting to Strip Path Prefix
Setting strip_path=false when the backend expects root paths causes routing errors. Configure per-route.
5. No Admin API Security
The admin API (port 8001) should not be exposed publicly. Use firewall rules or authentication.
Practice Questions
1. How does Kong's plugin architecture work?
Plugins hook into the request/response lifecycle. They are configured per service, route, or globally and can modify any aspect of the request.
2. What database options does Kong support?
PostgreSQL (production) and in-memory DB-less mode (dev/test). Cassandra support was deprecated.
3. How do you add authentication to a Kong service?
Enable the JWT, Key-Auth, OAuth2, or Basic-Auth plugin on the service, then configure credentials for consumers.
4. What is a Kong consumer?
A consumer represents a user or application that consumes the API. Consumers have credentials associated with them for authentication.
Challenge
Configure Kong with three services (users, orders, payments), add JWT authentication, rate limiting (100 req/min for users, 50 for orders, 20 for payments), and request logging plugin.
FAQ
Mini Project: Kong Configuration Generator
# kong_generator.py
import json
from typing import Dict, List, Optional
class KongConfigGenerator:
def __init__(self):
self.config = {"services": [], "routes": [], "plugins": []}
def add_service(self, name: str, url: str, retries: int = 5):
svc = {
"name": name, "url": url, "protocol": "http",
"retries": retries, "connect_timeout": 60000,
"write_timeout": 60000, "read_timeout": 60000,
}
self.config["services"].append(svc)
return svc
def add_route(self, service: str, paths: List[str],
methods: Optional[List[str]] = None,
strip_path: bool = True):
route = {
"service": {"name": service},
"paths": paths,
"strip_path": strip_path,
}
if methods:
route["methods"] = methods
self.config["routes"].append(route)
def add_plugin(self, service: str, name: str, config: dict):
self.config["plugins"].append({
"name": name,
"service": {"name": service},
"config": config,
})
def generate(self) -> str:
return json.dumps(self.config, indent=2)
gen = KongConfigGenerator()
gen.add_service("users", "http://users:3000")
gen.add_service("orders", "http://orders:4000")
gen.add_route("users", ["/api/users"], methods=["GET", "POST"])
gen.add_route("orders", ["/api/orders"], methods=["GET"])
gen.add_plugin("users", "rate-limiting", {"minute": 100})
gen.add_plugin("orders", "jwt", {})
config = json.loads(gen.generate())
print(f"Services: {len(config['services'])}")
print(f"Routes: {len(config['routes'])}")
print(f"Plugins: {len(config['plugins'])}")
for s in config['services']:
print(f" {s['name']} -> {s['url']}")
Expected output:
Services: 2
Routes: 2
Plugins: 2
users -> http://users:3000
orders -> http://orders:4000
What's Next
You understand Kong. Next, learn about NGINX as API gateway, then explore Envoy proxy.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro