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 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 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. 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 is available under http://localhost:/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 account service in the picture below. We can select source service in the combo box placed inside title panel.

swagger-1

Documentation appearence 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.

40 thoughts on “Microservices API Documentation with Swagger2

  1. 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.

    Like

  2. 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.

    Like

      1. 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.

        Like

      1. 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.

        Like

  3. 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;
    }

    Like

  4. 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?

    Like

  5. 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();
    }
    }

    Like

  6. 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;
    }
    }

    Like

  7. 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 ?

    Like

    1. 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;
      }

      Like

  8. 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

    Like

      1. 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
        }

        Like

      2. 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.

        Liked by 1 person

  9. 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.

    Like

    1. 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.

      Like

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

    Like

  11. 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

    Like

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

    Like

Leave a reply to Piotr Mińkowski Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.