Documenting REST API

Łukasz Monkiewicz
7 min readApr 16, 2021

Documentation is important, everybody knows this, especially when you are the one who needs to use some third-party service or library. Without good documentation it’s often a living hell, it’s like walking through a minefield. You carefully take step after step, praying that things won’t blow up. That’s why you HAVE to create documentation, especially for your libraries and REST services. Another important thing about documentation is that you have to update it whenever code changes because the only worse thing than no documentation is wrong documentation, a documentation that is not valid for the current code base.

So if everybody knows about this, it should be ok, right? Wrong. Developers hate creating documentation, and they do not do it unless it is really necessary or someone makes them do it — and unfortunately that statement is also true for me. Creating documentation is not bad, but maintaining it, may be hard, especially when you have a critical bug after bug to fix, and deadlines closing in — you just don’t have time to do it.

But there is hope… you can generate it!

Swagger

Swagger is quickly becoming the industry standard for documenting Rest APIs. It is becoming the WSDL of Rest services. Swagger definition consists of simple swagger.json file that describes all endpoints with their parameters, response codes, data types, param requirements, content types and important stuff that you may need to use a service. Also, just like in WSDL case, you can generate client and server application stub from the definition file. There are many tools that can help you with that. For example, you can head up to editor.swagger.io upload your definition file there and choose to generate source. You can choose from a wide range of languages and technologies, from Javascript, through Bash, Go, Groovy, Objective-C, Android, to plain Java, C# and others. All of this is available in many different technologies and flavors. This lets you really easy jump start your app development.

As a bonus, you can use swagger editor to design your API first, before you write any code.

Add Swagger to Spring Boot app

Spring boot is designed to simplify all possible spring development, designed to automatically do as much as possible. Thanks to all this, adding Swagger to your Spring Boot app is really simple.

First, you have to add springfox swagger plugin to your maven dependencies:

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

After this, we need to configure it to run. We have to create a Docket bean that hosts all swagger configuration options. You can do it any way you want, I prefer to do it by creating a separate spring configuration bean.

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

And that’s all that is needed for using swagger in its simplest form. Now just run your app, and open http://localhost:8080/swagger-ui.html in your browser. Swagger comes with HTML UI that renders your swagger.json file in really useful form. It presents all endpoints, all their parameters and data model. A really neat thing about this UI is an ability to invoke your services from it. You can use predefined JSON data or input your own and see the results.

If you need it, you can easily define for which endpoints documentation should be available using the paths method argument.

All except /error endpoints:

not(regex("/error.*"))

Only /notes endpoints:

ant("/notes/*")

Or combine them any way you want with and() and or() operators.

You can also add author and contact information to your generated docs. Do do this you need to specify ApiInfo property on your Docket configuration.

@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(onlyNotes())
.build();
}
public ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Example Notes API")
.description("Example API showing usege of Swagger for documenting Sprinv MVC RestControllers")
.contact(new Contact("Łukasz Monkiewicz", "https://lmonkiewicz.com", "lmonkiewicz@gmail.com"))
.version("1.0")
.build();
}

Add documentation to controllers

To generate more meaningful and useful documentation you need to provide some more information than spring-mvc annotation and code. Swagger comes with a small set of annotation that can help you with this.

If you want to name or describe the whole controller, you may use @Api annotation.

@RestController
@RequestMapping("/notes")
@Api(value = "Example of MVC controller with Swagger documentation",
description = "Note operation")
public class NotesController {
}

For endpoints, you can use @ApiOperation that lets you define content types, response types and some other basic things. Also, you can specify responses, their codes and meaning with @ApiResponses and @ApiResponse. And lastly, there is an @ApiParam for describing input parameters.

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ApiOperation("Find Note by given Id")
@ApiResponses({
@ApiResponse(code = 200, message = "Note with given id has been found and returned",
response = Note.class),
@ApiResponse(code = 404, message = "Note with given id does not exist")
})
public ResponseEntity<Note> getNote(@ApiParam(value = "Id of a Note", required = true)
@PathVariable(name = "id") Integer id){
if (data.containsKey(id)){
return ResponseEntity.ok(data.get(id));
}
else {
return ResponseEntity.notFound().build();
}
}

The final part of documenting you API, is your request and responses model. Swaggers can handle your classes really well without any annotations, it will show all the fields and their types. But to add some more meaning to it, you have to provide some more information. @ApiModel describes your whole class, gives it name and describes its relationships towards other data types. For annotating field, there is @ApiModelProperty annotation. With this, you can specify its description, its data type, mark field as required or read-only, define allowed values or provide an example value.

@ApiModel("Note data")
public class Note {
@ApiModelProperty(value = "ID of a note", example = "1", readOnly = true)
private Integer id;
@ApiModelProperty(value = "Title of note", example = "My important note!")
private String title;
@ApiModelProperty(value = "Content of note", example = "Buy milk", required = true)
private String content;
@ApiModelProperty("Is note marked as done?")
private boolean done = false;
}

Generate physical docs

Swagger UI and swagger.json file are both great, these two things can greatly improve development time. But something you also need a physical copy of the documentation. You may need a printed version for the client or for other third party that want’s to use your service. Json file won’t be a good solution here. But that can also be easily solved. We will use a JUnit test to generate documentation from our swagger.json file served by our application. I know it is a bit of a hack, but it is really simple, it works, and as a side effect, it tests the generation of your API specification, so it’s not so bad really.

To do this, first, we need to add a new maven dependency with a test scope, as we need it only for the testing phase.

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-staticdocs</artifactId>
<version>2.6.1</version>
<scope>test</scope>
</dependency>

After that, you can create our test.

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringSwaggerDocApplication.class)
public class Swagger2MarkupTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc; @Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void saveSwaggerJson() throws Exception {
this.mockMvc.perform(get("/v2/api-docs")
.accept(MediaType.APPLICATION_JSON))
.andDo(result -> {
File outputDir = new File("src/docs/swagger");
if (!outputDir.exists()){
outputDir.mkdirs();
}
try (FileOutputStream fos = new FileOutputStream("src/docs/swagger/swagger.json")) {
fos.write(result.getResponse().getContentAsByteArray());
fos.flush();
}
})
.andExpect(status().isOk());
}
@Test
public void convertSwaggerToAsciiDoc() throws Exception {
this.mockMvc.perform(get("/v2/api-docs")
.accept(MediaType.APPLICATION_JSON))
.andDo(Swagger2MarkupResultHandler.outputDirectory("src/docs/asciidoc").build())
.andExpect(status().isOk());
}
@Test
public void convertSwaggerToMarkdown() throws Exception {
this.mockMvc.perform(get("/v2/api-docs")
.accept(MediaType.APPLICATION_JSON))
.andDo(Swagger2MarkupResultHandler.outputDirectory("src/docs/markdown")
.withMarkupLanguage(MarkupLanguage.MARKDOWN).build())
.andExpect(status().isOk());
}
}

As you see, this test is not really complicated. It uses MockMVC to invoke spring rest endpoint (/v2/api-docs) to obtain swagger configuration. In the first test, it just saves the file to src/docs/swagger folder. The second and the third generate documentation in asciidoctor and markdown format respectively to separate folders.

If that’s not enough, we can go further and generate some other formats from asciidoctor docs. To do this, we need to add a new plugin to the maven build.

<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<dependencies>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj-pdf</artifactId>
<version>1.5.0-alpha.11</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctorj</artifactId>
<version>1.5.4.1</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>output-pdf</id>
<phase>test</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>pdf</backend>
<sourceHighlighter>rouge</sourceHighlighter>
<attributes>
<icons>font</icons>
<tabsize>4</tabsize>
<pagenums/>
<toc/>
<idprefix/>
<idseparator>-</idseparator>
</attributes>
<outputDirectory>src/docs/pdf</outputDirectory>
</configuration>
</execution>
</executions>
<configuration>
<sourceDirectory>src/docs/asciidoc</sourceDirectory>
<headerFooter>true</headerFooter>
</configuration>
</plugin>

This example adds additional processing of asciidoctor files in the test phase of a build. We use test phase, wich is a bit weird because that’s when the asciidoctor file will be generated. We configured here generation of PDF file.

For HTML output, you can add additional execution to the plugin, for example:

<execution>
<id>output-html</id>
<phase>test</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<sourceHighlighter>coderay</sourceHighlighter>
<backend>html</backend>
<attributes>
<toc/>
<linkcss>false</linkcss>
</attributes>
<outputDirectory>src/docs/html</outputDirectory>
</configuration>
</execution>

Conclusion

As you see, there are no excuses now for not creating your rest services documentation. It really can’t be any easier than this. With this setup, you can have many different formats created with each build. It even takes off some burden of maintaining documentation of you because it will fetch all changes in types, method signatures and changes in spring-mvc annotations. All you need to worry is basically a description of API. The whole working example is available in my GitHub repository https://github.com/lmonkiewicz/spring-swagger-doc.

--

--