Generate Swagger UI from Spring REST Docs
Spring REST docs and Swagger are great ways to document your API, but both have their strong and weak points. I prefer the test-driven way of documenting an API of Spring REST docs over the "Magic and Annotations" approach of Swagger, but on the other hand the output of Spring REST docs just doesn’t look as nice as the interactive UI of Swagger. Wouldn’t it be nice if we could combine both frameworks to create the most awesome API docs ever?
Let’s get started
First, we’ll need to create a new Spring Boot project, so I created a new project using Spring Initializr. Make sure you’ll add (at least) the following dependencies:
-
Spring Web
-
Spring REST Docs
For this demo I’ll use Gradle and Kotlin, but all tooling is also available for Maven and Java.
After you’ve generated a new project, open it in your favorite IDE, open build.gradle.kts
and remove the org.asciidoctor.convert
plugin and the asciidoctor
tasks as we won’t use them.
Create a simple REST endpoint
Of course we’ll need a REST endpoint to document, so create a file named HelloRestController.kt
with the following contents:
@RestController
class HelloRestController {
@GetMapping("/hello", produces = [MediaType.APPLICATION_JSON_VALUE])
fun getHello(@RequestParam("name", required = false) name: String?) =
HelloResponse(message = "Hello, ${name ?: "anonymous person"}")
}
data class HelloResponse(
val message: String
)
Once you’ve verified that your API is working correctly, let’s add some documentation to it!
Document the API in an OpenAPI spec
By default Spring REST Docs will output the documentation in Asciidoc format, but there’s an integration available which can output an OpenAPI spec. To add this integration to our project, copy these lines to the appropriate sections in your Gradle build file:
plugins {
id("com.epages.restdocs-api-spec") version "0.12.0" (1)
}
dependencies {
testImplementation("com.epages:restdocs-api-spec-mockmvc:0.12.0") (2)
}
openapi3 {
setServer("https://hello-service.example.com") (3)
title = "Hello Service API"
description = "An API providing greetings to users."
version = "1.0.0"
format = "yaml" (4)
}
1 | This plugin converts the output of Spring REST Docs into an OpenAPI spec. |
2 | This dependency adds support for OpenAPI3 spec files to Spring REST Docs. |
3 | Sets the URL of our service. |
4 | It is required to set this to yaml to generate the Swagger UI later on. |
Now that we’ve added the OpenAPI integration, let’s document the REST controller we created earlier. Create a unit test for the controller and add the following lines:
@AutoConfigureMockMvc
@ExtendWith(RestDocumentationExtension::class)
@WebMvcTest(HelloRestController::class)
class HelloRestControllerTest {
@Autowired
private lateinit var context: WebApplicationContext
@Autowired
private lateinit var mockMvc: MockMvc
@BeforeEach
fun setUp(restDocumentation: RestDocumentationContextProvider) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply<DefaultMockMvcBuilder>(
MockMvcRestDocumentation.documentationConfiguration(restDocumentation)
.operationPreprocessors()
.withResponseDefaults(Preprocessors.prettyPrint()) (1)
)
.build()
}
@Test
fun `document Hello endpoint`() {
mockMvc.perform(
RestDocumentationRequestBuilders.get("/hello?name={name}", "JDriven")
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().is2xxSuccessful)
.andDo(
document("hello-controller", (2)
snippets = arrayOf(
resource( (3)
ResourceSnippetParameters.builder()
.summary("Greets a user.")
.description("""
Every user of our systems wants to be greeted. That's why we came up with this endpoint.
Simply add the name of the user to the request to get a polite greeting.
""".trimIndent())
.responseSchema(GET_HELLO_RESPONSE_SCHEMA) (4)
.tag(GREETING_TAG) (5)
.requestParameters(
parameterWithName("name").optional()
.description("Name of the user. When omitted we'll assume the user wants to stay anonymous.")
)
.responseFields(
fieldWithPath("message").type(JsonFieldType.STRING).description("A personalised greeting.")
)
.build()
)
)
)
)
}
companion object {
private val GET_HELLO_RESPONSE_SCHEMA = Schema("get-hello-response")
private const val GREETING_TAG = "Greeting"
}
}
1 | Pretty print the generated snippets so that they are human-readable. |
2 | Make sure to use com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document here. |
3 | com.epages.restdocs.apispec.ResourceDocumentation.resource . |
4 | Defines a name for the response schema. When you’re documenting multiple requests which return the same response make sure this identifier is the same for every request. I recommend defining a constant to prevent typos. |
5 | Tags are used in Swagger UI to define subsections which group your API’s. |
As you can see the integration adds several new fields which are required to generate an OpenAPI spec.
Also, make sure to use the functions from the com.epages.restdocs.apispec.*
package to document fields and parameters, otherwise they won’t show up in the generated spec.
Next, run the unit test. If the test succeeds, a file named resource.json
should be added to the build/generated-snippets/hello-controller
directory of your Gradle project.
This file is used by the Gradle plug-in to generate an OpenAPI spec.
Let’s generate one by running:
./gradlew openapi3
After this, an OpenAPI 3 spec file named openapi3.yaml
should be created in buid/api-spec
.
Generate the Swagger UI
Using the generated OpenAPI spec we can now generate the Swagger UI. To do this we’ll need to add another Gradle plugin to our build file:
plugins {
id("org.hidetake.swagger.generator") version "2.18.2"
}
dependencies {
swaggerUI("org.webjars:swagger-ui:3.52.3") (1)
}
swaggerSources {
register("helloService").configure {
setInputFile(file("${project.buildDir}/api-spec/openapi3.yaml")) (2)
}
}
1 | Adds the Swagger UI sources to the project so the plugin can use them. |
2 | Location of the OpenAPI spec file generated by the openapi3 plugin. |
Now that we’ve added the plugin, simply run the following command to generate the Swagger UI:
./gradlew generateSwaggerUI
The result will be stored in directory build/swagger-ui-helloService
.
Tie everything together
Of course we want to ship the generated Swagger UI together with the application. To do this, we’ll need to add the generated Swagger UI to the Static content directory of our Spring Boot application. This can be done by adding some additional configuration and some tasks to our Gradle build file:
tasks {
withType<GenerateSwaggerUI> { (1)
dependsOn("openapi3")
}
register<Copy>("copySwaggerUI") { (2)
dependsOn("generateSwaggerUIHelloService")
val generateSwaggerTask = named<GenerateSwaggerUI>("generateSwaggerUIHelloService").get()
from("${generateSwaggerTask.outputDir}")
into("${project.buildDir}/resources/main/static")
}
withType<BootJar> {
dependsOn("copySwaggerUI") (3)
}
}
1 | Makes sure the OpenAPI spec file is generated before the Swagger UI is generated. |
2 | Create a new Copy task which copies the output of the generateSwaggerUI task to the static content folder of our Spring Boot application. |
3 | Makes sure our Swagger UI is copied into the application before the jar file is built. |
Now simply build the application, run it and browse to http://localhost:8080!
./gradlew build bootRun
All sources used in this blog post are available on Github: https://github.com/jorritvdven/jdriven-blog-springrestdocs-swagger.