Skip to content

a. Overview

The main functionality of Gateway Microservice is to route the incoming requests to the appropriate microservice, therefore, it is the entry point for all the incoming requests. In counterpart, each microservice have to expose their own port and endpoints to the internet, which is not a good practice. The Gateway Microservice will act as a reverse proxy and route the incoming requests to the appropriate microservice. Also, it will also handle the authentication and authorization of the incoming requests.

flowchart LR
    subgraph api [Trusted Layer]
        gateway e2@==> account
        gateway e4@==> others
        account --> db@{ shape: cyl, label: "Database" }
        others --> db
    end
    internet e1@==>|request| gateway:::red
    e1@{ animate: true }
    e2@{ animate: true }
    e4@{ animate: true }
    classDef red fill:#fcc

Advantages of using a Gateway Microservice:

  • It provides a single entry point for all the incoming requests, which makes it easier to manage and secure the application;
  • Only a single port needs to be exposed to the internet, which makes it easier to secure the application;
  • It can handle:
    • The authentication and authorization of the incoming requests, which makes it easier to secure the application;
    • The load balancing of the incoming requests, which makes it easier to scale the application;
    • The caching of the incoming requests, which makes it easier to improve the performance of the application;
    • The logging and monitoring of the incoming requests, which makes it easier to debug and troubleshoot the application;
    • The rate limiting of the incoming requests, which makes it easier to protect the application from DDoS attacks;

The key functionalities of Gateway Microservice are:

  • Routing: it will route the incoming requests to the appropriate microservice.
  • Authentication/Authorization: it will handle the authentication and the authorization of the incoming requests.

Slides

Click the slides and use ← β†’ to move through them; F goes full screen and P opens the presenter view, with the notes. Open the slides in a new tab

Gateway-Service

πŸ“ api
β”œβ”€β”€ πŸ“ gateway-service/
β”‚   β”œβ”€β”€ πŸ“ src/
β”‚   β”‚   └── πŸ“ main/
β”‚   β”‚       β”œβ”€β”€ πŸ“ java/
β”‚   β”‚       β”‚   └── πŸ“ store/
β”‚   β”‚       β”‚       └── πŸ“ gateway/
β”‚   β”‚       β”‚           β”œβ”€β”€  GatewayServiceApplication.java
β”‚   β”‚       β”‚           └──  GatewayResource.java
β”‚   β”‚       └── πŸ“ resources/
β”‚   β”‚           └── YAML application.yaml
β”‚   β”œβ”€β”€  pom.xml
β”‚   └── Docker Dockerfile
β”œβ”€β”€  .env
└── YAML compose.yaml

Source

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>store</groupId>
    <artifactId>gateway-service</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>25</java.version>
        <spring-cloud.version>2025.1.3</spring-cloud.version>
        <maven.compiler.proc>full</maven.compiler.proc>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway-server-webflux</artifactId>
        </dependency>

    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
spring:
  application:
    name: gateway

  cloud:
    gateway:
      server:
        webflux:

          default-filters:
            - RemoveRequestHeader=id-account
            - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

          globalcors:
            corsConfigurations:
              '[/**]':
                allowedOrigins: ${CORS_ALLOWED_ORIGINS:http://localhost,http://localhost:3000,http://localhost:8080}
                allowedHeaders: "*"
                allowedMethods: "*"
                allowCredentials: true

          routes:

            - id: insper-service
              uri: https://www.insper.edu.br
              predicates:
                - Path=/insper/**

            - id: account-service
              uri: http://account:8080
              predicates:
                - Path=/accounts/**

logging:
  level:
    root: info
    store: debug
    org.springframework.web: debug
package store.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayServiceApplication {

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

}
package store.gateway;

import java.util.Map;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GatewayResource {

    @GetMapping("/health-check")
    public ResponseEntity<Map<String, String>> healthCheck() {
        // only the container's hostname: it tells which replica answered
        // (never return System.getenv(): it would expose every secret)
        return ResponseEntity.ok(Map.of(
            "hostname", System.getenv().getOrDefault("HOSTNAME", "unknown")
        ));
    }

}
1
2
3
4
FROM eclipse-temurin:25
VOLUME /tmp
COPY target/*.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

Note that the Gateway Microservice is implemented using Spring Cloud Gateway, which is a library that provides a simple and effective way to route the incoming requests to the appropriate microservice. It also provides a lot of features such as authentication, authorization, load balancing, caching, logging, monitoring, and rate limiting.

Also, the Gateway Microservice is implemented using WebFlux, which is a reactive programming model that allows to handle a large number of concurrent requests with a small number of threads. This makes it easier to scale the application and improve the performance of the application.

Everything the gateway reads lives under spring.cloud.gateway.server.webflux β€” the prefix of Spring Cloud Gateway 5 (Spring Cloud 2025.1); a key left directly under spring.cloud.gateway is silently ignored:

Key What it does
routes Each route has an id, a uri and a Path predicate: /accounts/** goes to http://account:8080, the Compose service name of the account microservice.
globalcors CORS for every path, configured once. The allowed origins come from CORS_ALLOWED_ORIGINS, with the local front ends as default; allowCredentials: true lets the browser send cookies (the token of hands-on 3) β€” which is why the origins are listed and not *.
default-filters Filters applied to every route. RemoveRequestHeader=id-account drops any id-account header sent by the client, so only the gateway can set it (hands-on 3); DedupeResponseHeader removes the CORS headers duplicated when a service also sets them.

GatewayResource answers GET /health-check with the container's hostname, which shows which replica answered once there are several (hands-on 7). It returns that single variable on purpose: answering with System.getenv() would publish every environment variable β€” passwords and secrets included.

application.yaml

The routes point to the Compose service names (account), so the target containers must be running in the same Compose network; otherwise the gateway answers with an error for that route.

Also, a browser call from an origin that is not in allowedOrigins is blocked by CORS: set CORS_ALLOWED_ORIGINS for the front end you use.

After finishing the implementation of the Gateway Microservice, we can include it in the compose.yaml file and start it using Docker Compose. You can also test the Gateway Microservice by sending requests to the exposed port of the Gateway Microservice and checking if the requests are routed to the appropriate microservice.

compose.yaml
name: store

services:

  db:
    image: postgres:17
    volumes:
      - ${VOLUME_DB}:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${DB_USER:-store}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-store}
      POSTGRES_DB: ${DB_NAME:-store}

  account:
    build:
      context: ./account-service
      dockerfile: Dockerfile
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      DATABASE_DB: ${DB_NAME:-store}
      DATABASE_USERNAME: ${DB_USER:-store}
      DATABASE_PASSWORD: ${DB_PASSWORD:-store}
    deploy:
      replicas: 1
    depends_on:
      - db

  gateway:
    build:
      context: ./gateway-service
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    deploy:
      replicas: 1

Note that the other microservices declared in the compose.yaml file do not have its ports exposed to the internet, therefore, they can only be accessed through the Gateway Microservice.