OpenAPI or Swagger specification is the de facto standard for creating REST APIs. Almost every developer expects to see some Swagger documentation when works with APIs or doing integration. Apps made with Quarkus are not exceptions either. In this article, I go through how to add Swagger to Quarkus apps.
In the past two articles, we’ve covered how to create REST APIs and how to use MySQL with Quarkus. We implemented CRUD functionality for the User
resource that persists data to a MySQL database. The next step is to create API documentation for it.
Fortunately, the process is rather straightforward in Quarkus thanks to smallrye-openapi
library. So let’s begin.
Add Open API dependency
The very first step is to add the quarkus-smallrye-openapi
to the project. This library is built on top of the Eclipse MicroProfile OpenAPI specification. It generates OpenAPI dynamic schema and also has built-in Swagger UI.
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
</dependencies>
Now if you run the code and go to /openapi
, you should be able to download the specification in YML format. To change the path, add this configuration to application.properties
,
quarkus.smallrye-openapi.path=/swagger
Creating SwaggerConfig class
We don’t necessarily need to have this file, but it’s highly recommended. It contains annotations that provide information about the APIs purpose, maintainer, licensing, etc. Note that to make the annotations working, the SwaggerConfig.java
file should extend Application
from javax.ws.rs.core.Application
. That’s odd but necessary. Since the class is for the configuration only, it doesn’t require any implementation. An example of it would be like this,
package com.madadipouya.quarkus.example.config;
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
import org.eclipse.microprofile.openapi.annotations.info.Contact;
import org.eclipse.microprofile.openapi.annotations.info.Info;
import org.eclipse.microprofile.openapi.annotations.info.License;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import javax.ws.rs.core.Application;
@OpenAPIDefinition(
tags = {
@Tag(name = "user", description = "User operations."),
},
info = @Info(
title = "User API with Quarkus",
version = "0.0.1",
contact = @Contact(
name = "Kasra Madadipouya",
url = "http://geekyhacker.com/contact",
email = "[email protected]"),
license = @License(
name = "MIT",
url = "https://opensource.org/licenses/MIT"))
)
public class SwaggerConfig extends Application {
}
Enabling Swagger UI
Let’s enable the Swagger UI before proceeding further. For that open application.properties
file and enable Swagger UI as follows,
quarkus.swagger-ui.always-include=true
After that, Swagger UI is accessible at /swagger-ui
.
To provide a custom path, just override this parameter,
quarkus.swagger-ui.path=/swagger-ui.html
Document the APIs
For many developers, plain Swagger documentation is good enough. Though, in complex projects where many business terminologies are used, having more documented APIs are highly appreciated. One can achieve that by applying a set of annotations on controllers. In our case, we can enrich the User APIs with details such as status codes, description of APIs, and required fields as follows,
package com.madadipouya.quarkus.example.controller;
import com.madadipouya.quarkus.example.entity.User;
import com.madadipouya.quarkus.example.exception.UserNotFoundException;
import com.madadipouya.quarkus.example.exceptionhandler.ExceptionHandler;
import com.madadipouya.quarkus.example.service.UserService;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import javax.inject.Inject;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@Path("/v1/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserController {
private final UserService userService;
@Inject
public UserController(UserService userService) {
this.userService = userService;
}
@GET
@Operation(summary = "Gets users", description = "Lists all available users")
@APIResponses(value = @APIResponse(responseCode = "200", description = "Success",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = User.class))))
public List<User> getUsers() {
return userService.getAllUsers();
}
@GET
@Path("/{id}")
@Operation(summary = "Gets a user", description = "Retrieves a user by id")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Success",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = User.class))),
@APIResponse(responseCode = "404", description="User not found",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionHandler.ErrorResponseBody.class)))
})
public User getUser(@PathParam("id") int id) throws UserNotFoundException {
return userService.getUserById(id);
}
@POST
@Operation(summary = "Adds a user", description = "Creates a user and persists into database")
@APIResponses(value = @APIResponse(responseCode = "200", description = "Success",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = User.class))))
public User createUser(@Valid UserDto userDto) {
return userService.saveUser(userDto.toUser());
}
@PUT
@Path("/{id}")
@Operation(summary = "Updates a user", description = "Updates an existing user by id")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "Success",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = User.class))),
@APIResponse(responseCode = "404", description="User not found",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionHandler.ErrorResponseBody.class)))
})
public User updateUser(@PathParam("id") int id, @Valid UserDto userDto) throws UserNotFoundException {
return userService.updateUser(id, userDto.toUser());
}
@DELETE
@Path("/{id}")
@Operation(summary = "Deletes a user", description = "Deletes a user by id")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "Success"),
@APIResponse(responseCode = "404", description="User not found",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionHandler.ErrorResponseBody.class)))
})
public Response deleteUser(@PathParam("id") int id) throws UserNotFoundException {
userService.deleteUser(id);
return Response.status(Response.Status.NO_CONTENT).build();
}
@Schema(name="UserDTO", description="User representation to create")
public static class UserDto {
@NotBlank
@Schema(title="User given name", required = true)
private String firstName;
@NotBlank
@Schema(title="User surname", required = true)
private String lastName;
@Min(value = 1, message = "The value must be more than 0")
@Max(value = 200, message = "The value must be less than 200")
@Schema(title="User age between 1 to 200", required = true)
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User toUser() {
User user = new User();
user.setFirstName(firstName);
user.setLastName(lastName);
user.setAge(age);
return user;
}
}
}
Check here to see a list of existing annotations.
Fully functional example
As usual, you can find the fully functional example on my GitHub page at the link below,
https://github.com/kasramp/quarkus-rest-example
Thanks for reading and hope you’ve learned how to add Swagger to your Quarkus app.
Inline/featured images credits
- Kids cartoon by Tall N Small
- Quarkus logo by Quarkus
- Swagger logo on Wikipedia (CC BY-SA 4.0)