API Gateway for Kotlin Services: When One Entry Point is Better Than a Thousand
Imagine your Kotlin microservice zoo has grown to 20+ services. Each one reaches the world through its own port, requires its own authentication, and handles errors in its own way. Clients get confused by the endpoints, and every night you pray that an extra service doesn't crash and bury the entire application. Sound familiar?
There is a solution — API Gateway. This is a single entry point that handles routing, security, caching, and monitoring. Today we'll break down how to build such a gateway in Kotlin using two popular approaches: lightweight Ktor and industrial Spring Cloud Gateway. No fluff — just code and practice.
What is an API Gateway and Why Do You Need It?
API Gateway is a layer between clients and your microservices. Instead of every frontend or external application knowing the addresses of all your services, they only communicate with the gateway. And it decides where to route the request.
Main functions of the gateway:
- Routing — directs requests to the appropriate services based on URL or headers.
- Authentication and Authorization — checks tokens before the request reaches the business logic.
- Load Balancing — distributes requests among instances of the same service.
- Caching — stores responses for frequently requested data.
- Monitoring and Logging — collects metrics for all requests in one place.
- Rate Limiting — protects against DDoS and overloads.
If you're writing in Kotlin and using a microservices architecture, an API Gateway is a must-have, not an option. Without it, you risk chaos from scattered endpoints and security issues.
Approach 1: API Gateway on Ktor — Lightweight and Flexible
Ktor is a framework from JetBrains, built specifically for Kotlin. It's asynchronous, lightweight, and perfect for writing gateways when you don't want to drag in heavy Spring Boot.
Let's write a simple API Gateway in Ktor that routes requests to two services: user-service (users) and order-service (orders).
Step 1: Project Setup
Create a new Ktor project via start.ktor.io or add dependencies manually in build.gradle.kts:
dependencies { implementation("io.ktor:ktor-server-core:2.3.7") implementation("io.ktor:ktor-server-netty:2.3.7") implementation("io.ktor:ktor-client-core:2.3.7") implementation("io.ktor:ktor-client-cio:2.3.7") implementation("io.ktor:ktor-server-auth:2.3.7") implementation("io.ktor:ktor-server-content-negotiation:2.3.7") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7") implementation("ch.qos.logback:logback-classic:1.4.14")}Step 2: Basic Routing with Proxying
Let's create the main file Application.kt:
import io.ktor.client.*import io.ktor.client.request.*import io.ktor.client.statement.*import io.ktor.http.*import io.ktor.server.application.*import io.ktor.server.engine.*import io.ktor.server.netty.*import io.ktor.server.response.*import io.ktor.server.routing.*
fun main() { embeddedServer(Netty, port = 8080) { module() }.start(wait = true)}
fun Application.module() { val httpClient = HttpClient()
routing { // Proxy for user-service route("/api/users") { handle { val response = httpClient.get("http://localhost:8081${call.request.uri}") call.respondText( text