Microservices API Documentation with Swagger2

Microservices API Documentation with Swagger2

Swagger is the most popular tool for designing, building and documenting RESTful APIs. It has nice integration with Spring Boot. To use it in conjunction with Spring we need to add the following two dependencies to Maven pom.xml.

<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.6.1</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.6.1</version>
</dependency>

Swagger configuration for a single Spring Boot service is pretty simple. The level of complexity is greater if you want to create one documentation for several separated microservices. Such documentation should be available on API gateway. In the picture below you can see the architecture of our sample solution.

swagger

First, we should configure Swagger on every microservice. To enable it we have to declare @EnableSwagger2 on the main class. API documentation will be automatically generated from source code by Swagger library during application startup. The process is controlled by Docket @Bean which is also declared in the main class. The API version is read from pom.xml file using MavenXpp3Reader. We also set some other properties like title, author and description using apiInfo method. By default, Swagger generates documentation for all REST services including those created by Spring Boot. We would like to limit documentation only to our @RestController located inside pl.piomin.microservices.advanced.account.api package.

@Bean
public Docket api() throws IOException, XmlPullParserException {
   MavenXpp3Reader reader = new MavenXpp3Reader();
   Model model = reader.read(new FileReader("pom.xml"));
   return new Docket(DocumentationType.SWAGGER_2)
      .select()
      .apis(RequestHandlerSelectors.basePackage("pl.piomin.microservices.advanced.account.api"))
      .paths(PathSelectors.any())
      .build().apiInfo(new ApiInfo("Account Service Api Documentation", "Documentation automatically generated", model.getParent().getVersion(), null, new Contact("Piotr Mińkowski", "piotrminkowski.wordpress.com", "piotr.minkowski@gmail.com"), null, null));
}

Here’s our API RESTful controller.

@RestController
public class AccountController {

   @Autowired
   AccountRepository repository;

   protected Logger logger = Logger.getLogger(AccountController.class.getName());

   @RequestMapping(value = "/accounts/{number}", method = RequestMethod.GET)
   public Account findByNumber(@PathVariable("number") String number) {
      logger.info(String.format("Account.findByNumber(%s)", number));
      return repository.findByNumber(number);
   }

   @RequestMapping(value = "/accounts/customer/{customer}", method = RequestMethod.GET)
   public List findByCustomer(@PathVariable("customer") String customerId) {
      logger.info(String.format("Account.findByCustomer(%s)", customerId));
      return repository.findByCustomerId(customerId);
   }

   @RequestMapping(value = "/accounts", method = RequestMethod.GET)
   public List findAll() {
      logger.info("Account.findAll()");
      return repository.findAll();
   }

   @RequestMapping(value = "/accounts", method = RequestMethod.POST)
   public Account add(@RequestBody Account account) {
      logger.info(String.format("Account.add(%s)", account));
      return repository.save(account);
   }

   @RequestMapping(value = "/accounts", method = RequestMethod.PUT)
   public Account update(@RequestBody Account account) {
      logger.info(String.format("Account.update(%s)", account));
      return repository.save(account);
   }

}

The similar Swagger’s configuration exists on every microservice. API documentation UI is available under /swagger-ui.html. Now, we would like to enable one documentation embedded on the gateway for all microservices. Here’s Spring @Component implementing SwaggerResourcesProvider interface which overrides default provider configuration exists in Spring context.

@Component
@Primary
@EnableAutoConfiguration
public class DocumentationController implements SwaggerResourcesProvider {

   @Override
   public List get() {
      List resources = new ArrayList<>();
      resources.add(swaggerResource("account-service", "/api/account/v2/api-docs", "2.0"));
      resources.add(swaggerResource("customer-service", "/api/customer/v2/api-docs", "2.0"));
      resources.add(swaggerResource("product-service", "/api/product/v2/api-docs", "2.0"));
      resources.add(swaggerResource("transfer-service", "/api/transfer/v2/api-docs", "2.0"));
      return resources;
   }

   private SwaggerResource swaggerResource(String name, String location, String version) {
      SwaggerResource swaggerResource = new SwaggerResource();
      swaggerResource.setName(name);
      swaggerResource.setLocation(location);
      swaggerResource.setSwaggerVersion(version);
      return swaggerResource;
   }

}

All microservices api-docs are added as Swagger resources. The location address is proxied via Zuul gateway. Here’s gateway route configuration.

zuul:
  prefix: /api
     routes:
       account:
         path: /account/**
         serviceId: account-service
       customer:
         path: /customer/**
         serviceId: customer-service
       product:
         path: /product/**
         serviceId: product-service
       transfer:
         path: /transfer/**
         serviceId: transfer-service

Now, API documentation is available under gateway address http://localhost:8765/swagger-ui.html. You can see how it looks for the account-service in the picture below. We can select the source service in the combo box placed inside the title panel.

swagger-1

Documentation appearance can be easily customized by providing UIConfiguration @Bean. In the code below I changed default operations expansion level by setting “list” as a second constructor parameter – docExpansion.

@Bean
UiConfiguration uiConfig() {
   return new UiConfiguration("validatorUrl", "list", "alpha", "schema",
UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS, false, true, 60000L);
}

You can expand every operation to see the details. Every operation can be test by providing required parameters and clicking Try it out! button.

swagger-2

swagger-3

Sample application source code is available on GitHub.

42 COMMENTS

comments user
sri

please provide the source code example

    comments user
    Piotr Mińkowski

    Now I added link to source code in the end of article.

comments user
Ilya Zelenkevich

Hello, thanks for this guide. Why do you double API prefixes? I mean „/accounts” and „/api” in controllers. They are already in zuul config, isn’t it? I removed it in my case, have to use „/” value for mapping, but at least it doesn’t break API logic for frontend.

comments user
milkiR

Hi, Thank you for your tutorial 🙂 Microservice architecture is something new to me and I am having some problems running the sample code. Can you please leave some instructions.

    comments user
    Piotr Mińkowski

    What problems do you have? Maybe you would like to read my introduction to microservices with Spring Cloud which is available here: https://piotrminkowski.wordpress.com/2017/02/05/part-1-creating-microservice-using-spring-cloud-eureka-and-zuul/

      comments user
      milkiR

      Thank you! I found the introduction after asking the question, and managed to run the app.
      I have another question: Is there a way to make multiple Dockets on the backend app. I’ve tried that, but only the first docket has been applied to Swagger endpoints filtrating.
      Thank you in advance.

comments user
Adebowale

Excellent write up! One of the best write-ups that I have seen online.
Thank you.

comments user
Pavel Polushkin

Perfect article! Helped a lot to save time for investigation.

comments user
bambus

Awesome article! You have made a great job. Can you help me to implement swagger to vert.x api?

    comments user
    Piotr Mińkowski

    Anything in particular I can help you find?

      comments user
      bambus

      I have vert.x API GW and i want to implement swagger for it. I edited manually the swagger json file. Now, how to set on which route to see swagger-ui.html . For example http://localhost:8080/swagger-ui.html

        comments user
        Piotr Mińkowski

        If you use Vert.x I suggest you take a look on it: http://vertx.io/docs/vertx-web-api-contract/java/

          comments user
          bambus

          Is there a way to generate swagger-ui from vert.x code like from SpringBoot code? As i read there is the option to edit manually the swagger json and then to serve it from the app using the static resource handler and the swagger-ui app to render the documentation.

comments user
yassirelouarma

Hello,

Thank you for this nice article.
As an enhancement, I suggest to use ZuulProperties to iterate over routes dynamically.

@Autowired
private ZuulProperties properties ;

@Override
public List get() {
List resources = new ArrayList();
Map routes= zuulProperties.getRoutes();
routes.keySet().stream().forEach(route -> {
resources.add(buildResource(route,routes.get(route).getLocation()));
}

);
return resources;
}

    comments user
    Piotr Mińkowski

    Thanks. I think it’s a good idea 🙂

    comments user
    Revanth

    thanks for enhancement you suggested, As i had 14 microservices it is nice it takes from ZuulProperties

comments user
Abhijit

Hi Piotr,
Excellent article. Need some help to configure Swagger when the endpoints of microservices/gateway are protected by Spring security. The below configuration in WebSecurityConfigurerAdapter is allowing the Swagger request to go through but the Api documentation is not getting displayed (Blank page gets displayed)

protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers(“/token/*”,”/users/signup”,”/swagger-ui.html”).permitAll()
.anyRequest().authenticated()

Can you please help resolve this issue and update your example code with enabling Spring security?

    comments user
    Piotr Mińkowski

    I think you should add /v2/api-docs to antMatchers() method

comments user
Patricio Perez

Using Spring boot you could inject the BuildProperties bean, it has the artifact name, version and name extracted from pom.xml over build:

/**
* Exponer swagger GW
*
* @author pperez
* @since 30-01-2018
*/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

@Bean
public Docket api(BuildProperties build) {
return new Docket(SWAGGER_2)
.ignoredParameterTypes(OAuth2Authentication.class, Errors.class)
.apiInfo(apiInfo(build))
.select()
.apis(RequestHandlerSelectors.any())
.paths(Predicates.not(PathSelectors.ant(“/actuator/**”)))
.build();
}

private ApiInfo apiInfo(BuildProperties build) {
ApiInfoBuilder builder = new ApiInfoBuilder().title(build.getName())
.description(“Orquesta requests para microservicios Customer on Boarding chile”)
.version(build.getVersion());
return builder.build();
}
}

    comments user
    Piotr Mińkowski

    ok, thanks 🙂

      comments user
      Patricio Perez

      thank you for this article, it was just what i needed 🙂

comments user
Kevin Boger

Hi there!

I have followed the tutorial and the swagger docs are displaying correctly from the web service , but not from the Zuul gateway. The gateway docs appear to be the default endpoints provided by actuator. any thoughts on what could casue this ?

@Component
@Primary
@EnableAutoConfiguration
public class DocumentationController implements SwaggerResourcesProvider {

@Override
public List get() {
List resources = new ArrayList();
resources.add(swaggerResource(“catalog-service”, “/api/catalog/v2/api-docs”));
return resources;
}

private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(“2.0”);
return swaggerResource;
}
}

    comments user
    Kevin Boger

    Also, my springboot apps are all separate. not deployed as modules to a parent spring app.

      comments user
      Piotr Mińkowski

      It does not matter. It is important to register them in the same discovery as Zuul.

    comments user
    Piotr Mińkowski

    Hi,
    Could you provide your Zuul routes configuration?

comments user
Neeraj Jain

Hi Poitr,
Excellent article it helped me a lot. I do have one question in the Swagger UI where it lists all the available service names with their URLs how can we remove the url from it and display only service name for selection ?

    comments user
    Piotr Mińkowski

    You just have to remove service from that list:

    @Override
    public List get() {
    List resources = new ArrayList();
    resources.add(swaggerResource(“account-service”, “/api/account/v2/api-docs”, “2.0”));
    resources.add(swaggerResource(“customer-service”, “/api/customer/v2/api-docs”, “2.0”));
    resources.add(swaggerResource(“product-service”, “/api/product/v2/api-docs”, “2.0”));
    resources.add(swaggerResource(“transfer-service”, “/api/transfer/v2/api-docs”, “2.0”));
    return resources;
    }

comments user
Adib

Hi, thanks for wonderful article what can i do if i have @request-mapping in controller as /v1/account.
So my zuul path will be
Account:
Path: /v1/account/**
But what should i add in swagger resource

    comments user
    Piotr Mińkowski

    Hi Adib. Thanks. You need to set an address of your swagger api-docs. Unless you do not change app context path it is still /v1/api-docs

      comments user
      adib

      let me explain you my question in detail. i have a service called job service this is how i configured my zuul routes.

      zuul:
      prefix: ‘/api’
      routes:
      job-service:
      path: ‘/v1/jobs/**’
      stripPrefix: false
      i am using property file to add the service to swagger this is how i did.

      name: ‘Job Service’
      location: ‘/api/v1/jobs/v2/api-docs’
      version: ‘2.0’

      but it is not working. can you help me in it.
      My job-service looks like this:-

      @RestController
      @RequestMapping(“/v1/jobs”)
      @Api(tags = “Jobs Endpoint”, description = “Operations for managing Jobs”)
      public class JobsController {
      //simple curd operations
      }

        comments user
        Piotr Mińkowski

        Ok. You set @requestmapping on your controller, and it is available under path /v1/jobs/**, while swagger-api is still available under /v2/api-docs. Now, you set mapping for your service in zuul proxy to /v1/jobs/**. You also set stripPrefix to false what’s ok for your JobsController, because you don’t have to call it like that: /v1/jobs/v1/jobs/**. But for swagger if you call /v1/jobs/v2/api-docs zuul tries to forward it to address /v1/jobs/v2/api-docs on your service which is not available (it is available under /v2/api-docs). So, don’t set stripPrefix property to false and don’t set @RequestMapping on your controller.

comments user
Adib Rajiwate

Hi Piotr,
Thanks for your reply, now i am clear why my swagger was not working. But is there any option to get swagger work without removing stripPrefix and request-mapping.

    comments user
    Piotr Mińkowski

    Hi,
    Yes it is. You force Swagger to generate docs unders path with prefix /v1/jobs or create dynamic ZuulFilter which cuts unwanted prefix set by zuul from request forwarded to swagger api.

comments user
Neeraj Jain

Hi, Is there any way, so we can change Swagger UI logo ? Or disable the link which forwards it to swagger’s official website . ?

comments user
ritesh

followed each step but by request URL is not changing,ZUUL is running on 8888 and microservice on 8080
e.g localhost:8080/get/customer getting changed to localhost:8888/get/customer but in zuul the path mentioned is /api/** so its not hitting url.please suggest

    comments user
    Piotr Mińkowski

    In my example Zuul is available under port 8765.

comments user
anand jaisy

Thank you for the post it is very useful, how can I do the same functionality by using SPRING APIGATEWAY instead of ZULL

comments user
ivanpaz

Hi, I am having a problem to consume the services from the swagger ui. The methods is charging well, but when i try to consume always returns error:

“Undocumented
TypeError: Failed to fetch”

I cant understand what is happening ….

    comments user
    piotr.minkowski

    How do call it?

Leave a Reply