Wednesday, December 30, 2015

Documenting REST APIs with Swagger

Our REST API for managing geek diary entries is getting into shape. But APIs are meant to be used and making an API easy to use requires good documentation. In this post we will use Swagger for documenting our REST API. Swagger is a simple yet powerful representation of APIs. It maintains an ecosystem of tools that facilitate the easy documentation, client generation and discoverability of APIs. In this tutorial is presented how Swagger can be used to document a REST API and then the Swagger UI tool will be employed for beautiful web-based representation of the geek diaries API specification. I should note that the tutorial also uses the springfox framework which helps the generation of automated API documentation for APIs built with Spring.

Let's start by extending our pom.xml file with the necessary dependencies:

<!-- Swagger -->
<dependency>
  <groupId>io.swagger</groupId>
  <artifactId>swagger-core</artifactId>
  <version>1.5.4</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.3.0</version>
</dependency>
<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.3.0</version>
</dependency> 


There are few additions we need to to in our existing code base. We will create a new class named SwaggerConfig with some configuration and information about our REST API, we will declare the class in the WebAppInitializer and we will annotate our web service methods to be "picked up" by swagger. The project structure at the end will look like in the following figure:



The newly created SwaggerConfig class is as following:
package com.tasosmartidis.rest_api_tutorial.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any()) 
            .paths(PathSelectors.any()) 
            .build()
            .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        ApiInfo apiInfo = new ApiInfo(
            "Geek Diaries REST API",
            "This is a REST API for managing diary entries.",
            "v0.1",
            "API TOS",
            "martidis.tasos@gmail.com",
            "API License",
            "API License URL"
        );
        return apiInfo;
    }
}


Now we will update the WebAppInitializer to add the SwaggerConfig  in the servlet configuration classes:
package com.tasosmartidis.rest_api_tutorial.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {RootConfig.class, SpringMongoConfig.class, SecurityConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {WebConfig.class,SwaggerConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

}


Finally, we annotate our service methods to inform parties interested in our API about their purpose, input, output and similar. Besides the swagger annotations, the existing annotations for implementing the REST API are also utilized. The updated rest-controller class is shown below:
package com.tasosmartidis.rest_api_tutorial.web;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.tasosmartidis.rest_api_tutorial.data.DaoMongo;
import com.tasosmartidis.rest_api_tutorial.data.DiaryEntry;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
 
@RestController
@RequestMapping("/service/geek-diaries")
@Api(value = "/service/geek-diaries", description = "CRUD operations on the Geek Diaries datastore")
public class DiaryService {
    
    @Autowired
    DaoMongo dao; 
    
    @RequestMapping(value="/entry/{entryId}", method=RequestMethod.GET)
    @ApiOperation(value = "Retrieves a diary entry",
            notes = "The specified id is used to retrieve and return the diary entry",
            response = DiaryEntry.class)
    public ResponseEntity<DiaryEntry> getDiaryEntry(@PathVariable String entryId) {
        DiaryEntry diaryEntry = dao.getDiaryEntry(entryId);
        
        return new ResponseEntity<DiaryEntry>(diaryEntry,HttpStatus.OK); 
    }
    
    @RequestMapping(value="/entry/", method=RequestMethod.POST)
    @ApiOperation(value = "Creates a new diary entry that will be persisted in the database", 
            notes = "On success the newly created diary entry will be returned to the user along with the HTTP code",
            response = DiaryEntry.class)
    public ResponseEntity<DiaryEntry> createDiaryEntry(@RequestBody DiaryEntry newEntry) { 
         DiaryEntry newDiaryEntry = dao.createDiaryEntry(newEntry);

        return new ResponseEntity<DiaryEntry>(newDiaryEntry,HttpStatus.OK); 
    }
    
    @RequestMapping(value="/entry/", method=RequestMethod.PUT)
    @ApiOperation(value = "Updates diary entry if attributes have changed", 
            notes = "On success the updated diary entry will be returned to the user along with the HTTP code",
            response = DiaryEntry.class)
    public ResponseEntity<DiaryEntry> updateDiaryEntry(@RequestBody DiaryEntry newEntry) { 
         DiaryEntry updatedDiaryEntry = dao.updateDiaryEntry(newEntry);

        return new ResponseEntity<DiaryEntry>(updatedDiaryEntry,HttpStatus.OK); 
    }
    
    @RequestMapping(value="/entry/{entryId}", method=RequestMethod.DELETE)
    @ApiOperation(value = "Deletes a specified diary entry from the database", 
            notes = "Removes a diary entry based on its specified and unique id. On success the id is returned to requester",
            response = String.class)
    public ResponseEntity<String> deleteDiaryEntry(@PathVariable String entryId) { 
         String deletedDiaryEntry = dao.deleteDiaryEntry(entryId);

        return new ResponseEntity<String>(deletedDiaryEntry,HttpStatus.OK); 
    }
    
    @RequestMapping(value="/entry/", method=RequestMethod.GET) 
    @ApiOperation(value = "Retrieves all diary entries persisted in the database", 
            response = DiaryEntry.class,
            responseContainer = "List")
    public ResponseEntity<Map<String, DiaryEntry>> getDiaryEntries() { 
        Map<String, DiaryEntry> diaryEntries = dao.getAllDiaryEntries();

        return new ResponseEntity<Map<String, DiaryEntry>>(diaryEntries,HttpStatus.OK); 
    }
}


And that's about it! We package the application to a war and deploy on tomcat or similar. The use of the Swagger UI tool provides us with a very user friendly description of the API.

The online documentation can be find in the /root/path/of/api/swagger-ui.html. In my case it is http://localhost:9989/rest-api-tutorial/swagger-ui.html. What you would see looks something like this:

This is the generic high level overview, but by clicking in each method, a detailed specification and examples are presented. For example the POST method:

And the PUT method: 

















































Looks nice and handy don't you think? As a fellow geek I hope you document your REST APIs (with Swagger or not) because who knows, we may have to use eachother's APIs! The code is as always available in GitHub.

No comments:

Post a Comment