Cloud Native 14 min read

How to Expose Spring Cloud Microservices to Frontends with Spring Cloud Gateway

This article explains why a gateway is needed in a Spring Cloud microservice architecture, compares Spring Cloud Gateway with Zuul, introduces its core concepts of routes, predicates, and filters, and provides step‑by‑step code examples for dependency setup, configuration, custom authentication and logging filters, rate limiting, circuit breaking, troubleshooting tips, and a complete gateway configuration for a blog system.

Coder Trainee
Coder Trainee
Coder Trainee
How to Expose Spring Cloud Microservices to Frontends with Spring Cloud Gateway

Why a Gateway?

Without a gateway, clients must remember dozens of service URLs, configure CORS for each service, duplicate authentication logic, and any service address change forces client updates.

Gateway Architecture

Spring Cloud Gateway acts as the "big door" of the microservice system, providing a single entry point that forwards requests to backend services while handling unified authentication, logging, and rate limiting, and making service address changes transparent to clients.

Comparison with Zuul

Underlying platform: Zuul 1.x uses Servlet, Zuul 2.x uses Netty, Gateway uses WebFlux.

Blocking vs non‑blocking: Zuul 1.x is blocking, Zuul 2.x is partially non‑blocking, Gateway is fully non‑blocking.

Performance: low for Zuul 1.x, medium for Zuul 2.x, high for Gateway.

Maintenance: Zuul 1.x is discontinued, Zuul 2.x is inactive, Gateway is actively maintained.

Spring Cloud integration: good for Zuul 1.x, average for Zuul 2.x, native for Gateway.

Core Concepts

Gateway consists of three building blocks:

Route : defines the target service and path.

Predicate : evaluates conditions (e.g., path, method) to decide whether the route matches.

Filter : performs pre‑ and post‑processing such as header manipulation, logging, or retries.

Quick Start

1. Add Dependencies

<!-- gateway service pom.xml -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- Nacos service discovery -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- Note: do NOT add spring-boot-starter-web, it conflicts with Gateway -->

2. Basic Configuration

# gateway/src/main/resources/application.yml
server:
  port: 8080
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=0
        - id: article-service-route
          uri: lb://article-service
          predicates:
            - Path=/api/article/**
          filters:
            - StripPrefix=0
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

3. Verify

# Start Nacos
docker-compose up -d
# Start services
java -jar user-service.jar
java -jar article-service.jar
java -jar gateway.jar
# Test via gateway
curl http://localhost:8080/api/user/1   # → forwards to user-service:8081/api/user/1

Predicate Factory

Path – path matching (e.g., - Path=/api/user/**)

Method – HTTP method filter (e.g., - Method=GET,POST)

Header – request header pattern (e.g., - Header=X-API-Version, v\d+)

Query – query parameter pattern (e.g., - Query=page, \d+)

Cookie – cookie pattern (e.g., - Cookie=user-token, .+)

Host – host name pattern (e.g., - Host=**.blog.com)

RemoteAddr – client IP range (e.g., - RemoteAddr=192.168.0.0/24)

Before/After – time window (e.g., - Before=2025-12-31T23:59:59+08:00)

Filters

Built‑in Filters

AddRequestHeader / AddResponseHeader

AddRequestParameter

StripPrefix

RewritePath

Retry (e.g., 3 retries on INTERNAL_SERVER_ERROR)

RequestRateLimiter (Redis‑based rate limiting)

Custom Global Filter – Unified Authentication

@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Autowired
    private JwtUtils jwtUtils;
    private static final List<String> WHITE_LIST = Arrays.asList(
        "/api/user/login",
        "/api/user/register",
        "/api/article/public/**",
        "/actuator/**"
    );
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        // 1. White‑list pass
        if (isWhiteList(path)) {
            return chain.filter(exchange);
        }
        // 2. Get token
        String token = getToken(request);
        if (StringUtils.isBlank(token)) {
            return unauthorized(exchange, "Missing Token");
        }
        // 3. Validate token
        if (!jwtUtils.validateToken(token)) {
            return unauthorized(exchange, "Invalid or expired token");
        }
        // 4. Extract user info and inject headers
        Long userId = jwtUtils.getUserIdFromToken(token);
        String username = jwtUtils.getUsernameFromToken(token);
        ServerHttpRequest newRequest = request.mutate()
            .header("X-User-Id", String.valueOf(userId))
            .header("X-Username", username)
            .build();
        return chain.filter(exchange.mutate().request(newRequest).build());
    }
    private boolean isWhiteList(String path) {
        return WHITE_LIST.stream().anyMatch(path::startsWith);
    }
    private String getToken(ServerHttpRequest request) {
        String authHeader = request.getHeaders().getFirst("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }
    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        String body = JSON.toJSONString(Result.error(401, message));
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
    @Override
    public int getOrder() { return -100; } // highest priority
}

Custom Local Filter – Request Logging

@Component
@Slf4j
public class RequestLogGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        long startTime = System.currentTimeMillis();
        log.info("Request: {} {} from {}", request.getMethod(), request.getURI(), request.getRemoteAddress());
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            long duration = System.currentTimeMillis() - startTime;
            log.info("Response: {} {} - {} ({}ms)", request.getMethod(), request.getURI(), response.getStatusCode(), duration);
        }));
    }
    @Override
    public int getOrder() { return 0; }
}

Rate Limiting

Define a RedisRateLimiter bean (10 requests per second, burst capacity 20) and three KeyResolver s for IP, user ID, and API path. Apply the filter in application.yml with name: RequestRateLimiter and the chosen resolver.

Circuit Breaker

Include the Resilience4j starter, configure a circuit breaker named userServiceBreaker (slidingWindowSize 10, failureRateThreshold 50 %, waitDurationInOpenState 30 s, permittedNumberOfCallsInHalfOpenState 5). Add a CircuitBreaker filter to the user‑service route and a fallback controller that returns a 503 JSON response.

Full Configuration Example

# gateway/src/main/resources/application.yml
server:
  port: 8080
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_HOST:localhost}:8848
        namespace: blog-dev
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**,/api/auth/**
          filters:
            - StripPrefix=0
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@ipKeyResolver}"
                redis-rate-limiter.replenishRate: 20
                redis-rate-limiter.burstCapacity: 40
        - id: article-service
          uri: lb://article-service
          predicates:
            - Path=/api/article/**,/api/category/**
          filters:
            - StripPrefix=0
            - name: CircuitBreaker
              args:
                name: articleServiceBreaker
                fallbackUri: forward:/fallback/article
        - id: comment-service
          uri: lb://comment-service
          predicates:
            - Path=/api/comment/**
          filters:
            - StripPrefix=0
        - id: search-service
          uri: lb://search-service
          predicates:
            - Path=/api/search/**
          filters:
            - StripPrefix=0
      httpclient:
        connect-timeout: 3000
        response-timeout: 30s
    redis:
      host: localhost
      port: 6379
logging:
  level:
    org.springframework.cloud.gateway: DEBUG

Common Issues & Troubleshooting

1. Dependency Conflict

Gateway runs on WebFlux; having spring-boot-starter-web on the classpath causes a startup error ("Spring MVC found on classpath"). Remove the web starter and keep only spring-boot-starter-webflux.

2. Route Not Effective

When a request returns 404, check that the predicates paths are correct, ensure the target service is registered in Nacos, and enable TRACE logging for org.springframework.cloud.gateway to see routing decisions.

3. WebSocket Support

Add a route with Path=/ws/** and the appropriate uri (e.g., lb://websocket-service) to enable WebSocket proxying.

Next Episode Preview

Spring Cloud Microservice Practice (5): Nacos Configuration Center – unified configuration management, dynamic refresh, multi‑environment isolation, and version rollback.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

API Gatewayspring-bootGatewayCircuit Breakerspring-cloudrate-limiting
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.