WL
Java Full Stack Developer
Wassim Lagnaoui

API Gateway & Edge Routing

Learn how API Gateway simplifies client-service communication with routing, authentication, and traffic management.

What is an API Gateway?

An API Gateway is a server that acts as the single entry point for all client requests to your microservices architecture. It sits between external clients (web apps, mobile apps, third-party services) and your backend microservices, routing requests to the appropriate service instances. The gateway essentially acts as a reverse proxy that receives requests from clients, forwards them to one or more backend services, and then returns the appropriate response back to the client.

The gateway handles cross-cutting concerns that would otherwise need to be implemented in every microservice, such as authentication, authorization, rate limiting, request/response transformation, and monitoring. This centralization reduces code duplication and ensures consistent behavior across all services. In microservices architecture, the API Gateway is crucial because it provides a unified interface to clients while keeping the internal service structure flexible and allowing services to evolve independently.

Direct client-to-service calls are problematic because they create tight coupling between clients and services. Clients would need to know the exact network locations of every service, handle service discovery, implement retry logic for failed requests, and manage authentication with each service separately. When services are scaled, moved, or replaced, all clients would need updates, making the system brittle and difficult to maintain.

Additionally, direct calls expose internal service structure to external clients, making it harder to refactor or reorganize services without breaking client applications. The API Gateway solves these problems by providing a stable, unified interface that shields clients from the complexity of the underlying microservices infrastructure.


Core Responsibilities

Routing

The gateway examines incoming requests and determines which backend service should handle each request based on URL paths, HTTP methods, headers, or other request attributes. It maintains routing rules that map external API endpoints to internal service locations, allowing clients to use simple, consistent URLs while the gateway handles the complexity of finding and connecting to the appropriate service instances.

Load Balancing

When multiple instances of a service are running, the gateway distributes incoming requests across these instances to ensure optimal resource utilization and prevent any single instance from becoming overwhelmed. It can use various load balancing algorithms like round-robin, least connections, or weighted distribution to intelligently route traffic based on current system conditions.

Authentication/Security Offloading

The gateway centralizes authentication and authorization logic, validating user credentials and permissions before requests reach backend services. This means backend services can trust that all incoming requests are already authenticated and authorized, simplifying their implementation and ensuring consistent security policies across the entire system. The gateway can handle various authentication methods like JWT tokens, OAuth2, API keys, or session-based authentication.

Rate Limiting

To protect backend services from being overwhelmed by too many requests, the gateway implements rate limiting policies that control how frequently clients can make requests. It can set different limits for different types of clients (free vs premium users, internal vs external applications) and use various algorithms like token bucket or sliding window to provide both burst capacity and sustained rate control while returning appropriate error responses when limits are exceeded.


Spring Cloud Gateway Basics

Spring Cloud Gateway is preferred over traditional servlet-based gateways because it's built on Spring Framework 5's reactive stack with Project Reactor and Spring WebFlux. This reactive, non-blocking architecture allows it to handle thousands of concurrent connections with a small number of threads, making it highly efficient for gateway scenarios where you need to process many simultaneous requests and potentially aggregate responses from multiple backend services.

The gateway integrates seamlessly with the Spring ecosystem, including Spring Boot auto-configuration, Spring Security for authentication, Spring Cloud service discovery, and Spring Actuator for monitoring. This tight integration means you can leverage existing Spring knowledge and configuration patterns while getting enterprise-grade features out of the box.

Basic Route Configuration (application.yml)

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1

In this configuration, the id is a unique identifier for the route, the uri specifies the destination (lb:// indicates load-balanced service discovery), predicates define the conditions that must be met for this route to match a request, and filters specify transformations to apply to requests or responses passing through this route.

The StripPrefix=1 filter removes the first path segment (/api) before forwarding to the backend service, so a request to /api/users/123 becomes /users/123 when sent to the user-service. This allows the gateway to use different URL structures externally while maintaining clean, service-specific paths internally.


Route Predicates

Route predicates are conditions that determine whether an incoming request matches a specific route. They act as the decision-making logic that examines request characteristics like path, method, headers, query parameters, or even the time of day, and decide if the request should be handled by a particular route. Predicates are essential because they allow you to create sophisticated routing rules that can handle complex scenarios like API versioning, feature flags, canary deployments, or geographic routing.

Spring Cloud Gateway provides many built-in predicates for common routing scenarios, and you can combine multiple predicates using logical AND operations to create precise routing rules. This flexibility allows you to implement advanced traffic management strategies without writing custom routing logic.

Path Predicate Example

spring:
  cloud:
    gateway:
      routes:
        - id: order-route
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

This Path predicate matches any request where the URL path starts with /api/orders/, including /api/orders/123, /api/orders/search, or /api/orders/user/456. The ** wildcard matches multiple path segments, making it perfect for routing entire API sections to specific services.

Combining Multiple Predicates

spring:
  cloud:
    gateway:
      routes:
        - id: admin-route
          uri: lb://admin-service
          predicates:
            - Path=/api/admin/**
            - Header=X-Admin-Token, .+
            - Method=GET,POST

When multiple predicates are specified, ALL conditions must be met for the route to match (logical AND). This example only routes requests that have a path starting with /api/admin/, contain an X-Admin-Token header with any value, and use either GET or POST methods. This allows you to create secure, method-specific routing rules that ensure only properly authenticated admin requests reach sensitive endpoints.


Filters

Filters in Spring Cloud Gateway provide a powerful way to modify requests and responses as they pass through the gateway. Pre-filters execute before the request is sent to the backend service and can modify request headers, validate input, add authentication context, or reject requests entirely. Post-filters execute after the backend service responds and can modify response headers, transform response bodies, add security headers, or collect metrics.

Filters enable you to implement cross-cutting concerns consistently across all your routes without duplicating code in backend services. They operate in a configurable order, allowing you to create processing pipelines that handle authentication, then authorization, then request transformation, then rate limiting, in the exact sequence your application requires.

Adding Headers Filter

spring:
  cloud:
    gateway:
      routes:
        - id: api-route
          uri: lb://backend-service
          filters:
            - AddRequestHeader=X-Request-Source, gateway
            - AddResponseHeader=X-Response-Time, #{T(System).currentTimeMillis()}

The AddRequestHeader filter adds a custom header to every request before forwarding it to the backend service, which helps backend services identify that requests came through the gateway. The AddResponseHeader filter adds metadata to responses, such as processing timestamps that can be useful for performance monitoring and debugging.

Rate Limiter Filter

spring:
  cloud:
    gateway:
      routes:
        - id: rate-limited-route
          uri: lb://api-service
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20

The RequestRateLimiter filter implements token bucket algorithm using Redis to track request counts across multiple gateway instances. The replenishRate controls how many requests per second are allowed on average, while burstCapacity allows temporary spikes above the sustained rate, providing flexibility for realistic traffic patterns while protecting backend services from being overwhelmed.

Java DSL Filter Example

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("custom-route", r -> r.path("/api/**")
            .filters(f -> f
                .addRequestHeader("X-Gateway-Version", "1.0")
                .circuitBreaker(config -> config.setName("myCircuitBreaker"))
                .retry(3))
            .uri("lb://backend-service"))
        .build();
}

This Java DSL approach provides programmatic configuration with full IDE support and type safety. The example shows how to chain multiple filters including custom headers, circuit breaker for resilience, and retry logic for handling transient failures, all configured fluently in a single route definition.


Authentication & Security Offloading

The API Gateway handles authentication and security because it's much more efficient and secure to validate user credentials once at the edge rather than in every microservice. When the gateway validates authentication tokens, extracts user identity, and adds trusted headers to requests, backend services can focus on business logic instead of repeatedly implementing the same security checks. This pattern also ensures consistent security policies across all services and makes it easy to update authentication methods without changing every service.

Security offloading also enables sophisticated security policies like multi-factor authentication, geo-blocking, or dynamic authorization rules that would be complex and expensive to implement consistently across dozens of microservices. The gateway becomes the security enforcement point that protects your entire service mesh with centralized, auditable security policies.

JWT Validation Example

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: https://auth-server.com/.well-known/jwks.json
  cloud:
    gateway:
      routes:
        - id: secure-route
          uri: lb://protected-service
          predicates:
            - Path=/api/secure/**
          filters:
            - RemoveRequestHeader=Authorization

This configuration sets up the gateway as an OAuth2 Resource Server that validates JWT tokens against the specified JSON Web Key Set endpoint. When a valid JWT is presented, the gateway extracts user information and can add trusted headers like X-User-ID or X-User-Roles before forwarding the request. The RemoveRequestHeader filter ensures that the original Authorization header is not passed to backend services, preventing potential security issues if those services have their own token validation logic.

Custom Authentication Filter

@Component
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = extractToken(exchange.getRequest());

        if (isValidToken(token)) {
            String userId = extractUserId(token);
            ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                .header("X-User-ID", userId)
                .header("X-Authenticated", "true")
                .build();

            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        } else {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }

    @Override
    public int getOrder() { return -100; }
}

This custom filter demonstrates how to implement authentication logic that validates tokens and enriches requests with user context. The filter extracts JWT tokens from request headers, validates them against your authentication service or local validation logic, and either adds trusted user information to the request or rejects unauthorized requests immediately. The low order value (-100) ensures this filter runs early in the filter chain, before business logic filters that might depend on authentication context.


Putting it All Together

The main benefits of API Gateway include simplicity for clients who only need to know one endpoint instead of tracking multiple service locations, security through centralized authentication and authorization that ensures consistent policies across all services, and traffic management capabilities like rate limiting, load balancing, and circuit breaking that protect your system from overload and failures while providing excellent observability into request patterns and system health.

By centralizing cross-cutting concerns, the gateway reduces code duplication, simplifies client development, and enables advanced deployment strategies like blue-green deployments or canary releases without requiring changes to client applications. The gateway becomes the operational control point for your entire API surface, providing unified monitoring, logging, and security enforcement.

Spring Boot Implementation

Creating a complete API Gateway with Spring Boot requires setting up dependencies, configuring routes, and implementing custom filters. Here's a minimal but production-ready gateway implementation that demonstrates all the key concepts we've covered.

Maven Dependencies (pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
</dependencies>

Main Application Class

@SpringBootApplication
@EnableEurekaClient
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service", r -> r.path("/api/users/**")
                .filters(f -> f
                    .stripPrefix(1)
                    .addRequestHeader("X-Gateway-Source", "api-gateway")
                    .requestRateLimiter(config -> config
                        .setRateLimiter(redisRateLimiter())
                        .setKeyResolver(userKeyResolver())))
                .uri("lb://user-service"))
            .route("order-service", r -> r.path("/api/orders/**")
                .and().method(HttpMethod.GET, HttpMethod.POST)
                .filters(f -> f
                    .stripPrefix(1)
                    .circuitBreaker(config -> config
                        .setName("order-cb")
                        .setFallbackUri("forward:/fallback/orders")))
                .uri("lb://order-service"))
            .build();
    }

    @Bean
    public RedisRateLimiter redisRateLimiter() {
        return new RedisRateLimiter(10, 20, 1);
    }

    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> exchange.getRequest().getHeaders()
            .getFirst("X-User-ID") != null ?
            Mono.just(exchange.getRequest().getHeaders().getFirst("X-User-ID")) :
            Mono.just("anonymous");
    }
}

Complete Gateway Configuration

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Service-Source, gateway
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
            - Method=GET,POST
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: order-cb
                fallbackUri: forward:/fallback/orders

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

management:
  endpoints:
    web:
      exposure:
        include: health,info,gateway
  endpoint:
    gateway:
      enabled: true

This comprehensive example demonstrates a production-ready gateway configuration that routes requests to multiple services using service discovery, implements rate limiting to protect against overload, includes circuit breaker patterns for resilience against service failures, and adds tracking headers for observability. The configuration shows how routing, security, and operational concerns come together in a single, manageable configuration that provides robust, scalable API management for your microservices architecture.


Lesson Summary

In this lesson, we explored API Gateway patterns and Spring Cloud Gateway implementation for microservices architectures. Here's a comprehensive recap of all the key concepts covered:

API Gateway Fundamentals

  • Purpose: Single entry point for all client requests, acting as a reverse proxy between clients and microservices
  • Problem solved: Eliminates direct client-to-service coupling, reduces complexity, and centralizes cross-cutting concerns
  • Benefits: Unified interface, simplified client development, and flexible internal service architecture
  • Architecture: Gateway shields clients from internal service structure and handles service evolution transparently

Core Gateway Responsibilities

  • Routing: Examines requests and directs them to appropriate backend services based on URL paths, methods, or headers
  • Load Balancing: Distributes requests across multiple service instances using various algorithms for optimal resource utilization
  • Security Offloading: Centralizes authentication and authorization, ensuring consistent security policies across all services
  • Rate Limiting: Protects backend services from overload using token bucket algorithms and configurable client-specific limits

Spring Cloud Gateway

  • Architecture: Built on reactive Spring WebFlux for non-blocking, high-throughput request processing
  • Configuration: Supports both YAML declarative and Java DSL programmatic route configuration
  • Integration: Seamless integration with Spring ecosystem, Eureka service discovery, and Spring Security
  • Dependencies: spring-cloud-starter-gateway with Spring Cloud BOM for version management

Route Predicates

  • Function: Conditional logic that determines if a request matches a specific route based on request characteristics
  • Types: Path, Method, Header, Query Parameter, Host, and Time-based predicates for flexible routing rules
  • Combination: Multiple predicates use logical AND operations for precise routing control
  • Use cases: API versioning, feature flags, canary deployments, and geographic routing strategies

Gateway Filters

  • Pre-filters: Modify requests before forwarding to backend services - authentication, validation, header manipulation
  • Post-filters: Modify responses after backend processing - response headers, metrics collection, response transformation
  • Built-in filters: AddRequestHeader, RemoveRequestHeader, RequestRateLimiter, CircuitBreaker, and Retry filters
  • Custom filters: GlobalFilter interface for cross-cutting concerns and AbstractGatewayFilterFactory for route-specific logic

Authentication & Security

  • Centralized security: Single point for authentication validation eliminates duplication across microservices
  • JWT integration: OAuth2 Resource Server configuration for token validation with JWK Set endpoints
  • Header enrichment: Add trusted user context headers (X-User-ID, X-User-Roles) for backend services
  • Custom filters: Implement sophisticated authentication logic with proper error handling and fallback mechanisms

Production Implementation

  • Maven dependencies: Gateway starter, Eureka client, and reactive Redis for rate limiting functionality
  • Resilience patterns: Circuit breakers, rate limiting, and retry logic for handling service failures gracefully
  • Observability: Actuator endpoints for monitoring routes, health checks, and operational metrics
  • Configuration management: Environment-specific settings for different deployment stages and service URLs

Key Takeaways

  • API Gateway is essential for managing complexity in microservices architectures and providing unified client interfaces
  • Spring Cloud Gateway offers production-ready features with reactive architecture for high-performance scenarios
  • Proper security offloading simplifies backend services while ensuring consistent authentication and authorization policies
  • Combining routing predicates and filters enables sophisticated traffic management and operational control
  • Gateway becomes the operational control point for monitoring, security, and traffic management across your entire API surface