Skip to content

This project is a Spring Boot application that demonstrates API documentation using Springdoc OpenAPI.

Notifications You must be signed in to change notification settings

SimonElbrink/api-documentation-springdoc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Documenting REST API with Swagger in Spring Boot

API documentation is an indispensable part of developing web applications. Luckily, there are solutions that make meticulous manual documentation crafting a thing of the past.

This step-by-step tutorial will guide you through integrating Swagger (based on OpenAPI 3.0 specification) into a Spring Boot project.

What is Swagger?

Swagger is a set of tools that help developers to create, edit, and use API documentation according to the OpenAPI specification. With Swagger, it is no longer necessary to manually write lengthy API docs: the solution is capable of reading the structure of the API you defined in the annotations of your code and automatically converting it into API specification.

What is more, Swagger provides a user interface that generates interactive API documentation that lets users test the API calls in the browser.

Main Swagger components are:

  • Swagger Editor for writing and editing API specs,
  • Swagger UI for creating interactive API documentation,
  • Swagger Codegen for generating server stubs or client libraries for your API.

Swagger vs OpenAPI

Both terms, Swagger and OpenAPI, are used in the context of API documentation, but they are not the same. OpenAPI is a standard specification for describing API, and Swagger helps to create API docs in line with this specification.

Integrating Swagger into a Spring Boot project

Prerequisites

  • JDK 17 or later
  • Maven
  • Your favorite IDE (preferably IntelliJ IDEA)

Create a sample REST API project

Skip this step if you want to use your own project. You can follow along or implement the examples from the tutorial into your application accordingly.

The demo Spring Boot application will expose REST APIs for managing employees. The Employee will have id, first name, and last name. Our APIs will allow for getting a list of all employees, getting one employee, adding a new employee, updating the existing employee, and deleting an employee according to the following schema:

Method URL Action
GET /employees Get a list of all employees
GET /employees/{employeeId} Get one employee by id
POST /employees Add an employee
PUT /employees Update an employee
DELETE /employees/{employeeId} Delete an employee

Head to Spring Initializr to create a skeleton of your project. Select Java, Maven, define a project name and choose Java 17.

We will also need Spring Web, Lombok, Spring Data JDBC, H2 Database Driver, and DevTools dependencies. DevTools is a great time saver because you don’t have to restart the application every time you introduce changes, the recompilation is performed on the fly.

Generate the project and open it in your IDE.


Let’s keep the structure super simple. We will need only two classes, Employee and EmployeeController, and an EmployeeRepository interface.

Populate the classes as shown below.

Employee

@Getter
@Setter
@NoArgsConstructor
public class Employee {

   @Id
   private int id;
   private String firstName;
   private String lastName;

   public Employee(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

EmployeeRepository

public interface EmployeeRepository extends ListCrudRepository<Employee, Integer> { }

EmployeeController

@RestController
public class EmployeeController {
   @Autowired
   private EmployeeRepository repository;

   public EmployeeController(EmployeeRepository repository) {
       this.repository = repository;
   }

   @GetMapping("/employees")
   public List<Employee> findAllEmployees() {
       return repository.findAll();
   }

   @GetMapping("/employees/{employeeId}")
   public Employee getEmployee(@PathVariable int employeeId) {
       Employee employee = repository.findById(employeeId)
        .orElseThrow(
        () -> new RuntimeException("Employee id not found - " + employeeId));
        
       return employee;
   }

   @PostMapping("/employees")
   public Employee addEmployee(@RequestBody Employee employee) {
       employee.setId(0);
       Employee newEmployee = repository.save(employee);
       return newEmployee;
   }
  
   @PutMapping("/employees")
   public Employee updateEmployee(@RequestBody Employee employee) {
       Employee theEmployee = repository.save(employee);
       return theEmployee;
   }
}

@DeleteMapping("/employees/{employeeId}")
public String deleteEmployee(@PathVariable int employeeId) {
   Employee employee = repository.findById(employeeId)
    .orElseThrow(
    () -> new RuntimeException("Employee id not found - " + employeeId));
    
   repository.delete(employee);
   return "Deleted employee with id: " + employeeId;
}

Database setup

Now, let’s provide a database schema. Create a schema.sql file in the resources directory with the following content:

CREATE TABLE IF NOT EXISTS employee (  
id BIGINT PRIMARY KEY AUTO_INCREMENT,  
first_name VARCHAR(255) NOT NULL,  
last_name VARCHAR(255) NOT NULL  
);

Next, let’s populate our database instance with some data (that’s optional, we don’t need to work with DB data when developing APIs, I just don’t like to see the ugly error page upon starting the app in the browser). Create the data.sql file in resources with the following content:

delete from employee;
insert into employee (first_name, last_name) values ('John', 'Doe');
insert into employee (first_name, last_name) values ('Jane', 'Smith');

The last thing to do is to configure your application.properties file.

spring.sql.init.mode=always

because we are performing a script-based initialization.

Finally, let’s verify that the app is functioning as desired. Run it from the TestOpenapiApplication class

You should see the following result at http://localhost:8080/employees

[
{
"id": 1,
"firstName": "John",
"lastName": "Doe"
},
{
"id": 2,
"firstName": "Jane",
"lastName": "Smith"
}
]

That’s it! Our minimalistic CRUD application is ready for experiments.


Add springdoc-openapi dependency

To work with Swagger, we need the springdoc-api library that helps to generate OpenAPI-compliant API documentation for Spring Boot projects. The library supports Swagger UI and other useful features such as OAuth2.

Add the following dependency for springdoc-api to your pom.xml file:

<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  <version>2.6.0</version>
</dependency>

That’s all, no additional configuration is required!

Generate API documentation

The OpenAPI documentation is generated when we build our project. So let’s verify that everything is working correctly. Run your application and go to the default page where the API documentation is located: http://localhost:8080/v3/api-docs.

You should see the data on your endpoints in JSON format. You can also access the .yaml file at http://localhost:8080/v3/api-docs.yaml.

It is possible to change the default path in the application.properties file. For example:

springdoc.api-docs.path=/api-docs

Now the documentation is available at http://localhost:8080/api-docs.

Integrate Swagger UI

The beauty about springdoc-openapi library dependency is that it already includes Swagger UI, so we don’t have to configure the tool separately!

You can access Swagger UI at http://localhost:8080/swagger-ui/index.html, where you will see a beautiful user interface to interact with your endpoints.

swagger-ui-non-configured.png

It is also possible to change the default path for Swagger UI. In the application.properties if needed.

springdoc.swagger-ui.path=/swagger-ui.html

Configure Swagger 3 in Spring Boot with annotations

Right now, our API documentation is not very informative. We can extend it with the help of annotations added to the application code. Below is the summary of the most common ones.

Add Swagger API description

First of all, let’s include some essential data about the API, such as name, description, and author contacts. For that purpose, create an OpenAPIConfiguration class and fill in the following code:

OpenAPIConfiguration

@Configuration
public class OpenAPIConfiguration {

   @Bean
     public OpenAPI defineOpenApi() {
		Server server = new Server()  
	        .url("http://localhost:8080")  
	        .description("Development Server");  
	        
		Contact contact = new Contact()  
	        .name("Simon")  
	        .email("[email protected]");  
	        
		Info info = new Info()  
	        .title("Employee Management API")  
	        .version("1.0")  
	        .description("API to manage employees")  
	        .contact(contact);  
	        
		return new OpenAPI().info(info).servers(List.of(server));
   }
}

You can also fill in information about applicable License and some other data, but code above is enough for demonstration. Run the app and verify that the main API page includes provided information:

swagger-ui-main-page.png

Been validation

The springdoc-openapi library supports JSR 303: Bean Validation (@NotNull, @Min, @Max, and @Size), so when we add these annotations to our code, the additional schema documentation will be automatically generated.

Let’s specify them in Employee class:

public class Employee {
    @Id
    @NotNull
    private int id;

    @NotNull
    @Size(min = 1, max = 20)
    private String firstName;

    @NotNull
    @Size(min = 1, max = 50)
    private String lastName;
}

When you recompile your app, you will see that the Schemas section contains the specified info:

schema-bean-validation.png

@Tag annotation

The @Tag annotation can be applied at class or method level and is used to group the APIs in a meaningful way.

For instance, let’s add this annotation to our GET methods:

@Tag(name = "get", description = "GET methods of Employee APIs")
@GetMapping("/employees")
public List<Employee> findAllEmployees() {
   return repository.findAll();
}

@Tag(name = "get", description = "GET methods of Employee APIs")
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@PathVariable int employeeId) {
   Employee employee = repository.findById(employeeId)
           .orElseThrow(() -> new RuntimeException("Employee id not found - " + employeeId));
   return employee;
}

You will see that APIs are now grouped differently:

Tag-annotation.png

@Operation annotation

The @Operation annotation enables the developers to provide additional information about a method, such as summary and description.

Let’s update our updateEmployee() method:

@Operation(summary = "Update an employee",
       description = "Update an existing employee. The response is updated Employee object with id, first name, and last name.")
@PutMapping("/employees")
public Employee updateEmployee(@RequestBody Employee employee) {
   Employee theEmployee = repository.save(employee);
   return theEmployee;
}

The API description in Swagger UI is now a little more informative:

operation-annotation.png

@ApiResponses annotation

The @ApiResponses annotation helps to add information about responses available for the given method. Each response is specified with @ApiResponse, for instance

@ApiResponses({
@ApiResponse(responseCode = "200", 
			 content = { @Content(mediaType = "application/json",
			 schema = @Schema(implementation = Employee.class)) }),
@ApiResponse(responseCode = "404", 
			 description = "Employee not found",
			 content = @Content) })

@DeleteMapping("/employees/{employeeId}")
public String deleteEmployee(@PathVariable int employeeId) {
   Employee employee = repository.findById(employeeId)
	.orElseThrow(
	() -> new RuntimeException("Employee id not found - " + employeeId));
   repository.delete(employee);
   return "Deleted employee with id: " + employeeId;
}

After you recompile the Controller class, the data on responses will be automatically generated:

api-response-annotation.png

@Parameter annotation

The @Parameter annotation can be used on a method parameter to define parameters for the operation. For example,

public Employee getEmployee(
	@Parameter(description = "ID of employee to be retrieved", required = true)
    @PathVariable int employeeId) {
   Employee employee = repository.findById(employeeId)
           .orElseThrow(
           () -> new RuntimeException("Employee id not found - " + employeeId));
   return employee;
}

Here, the description element provides additional data on parameter purpose, and required is set to true signifying that this parameter is mandatory.

And here’s how it looks in Swagger UI:

parameter-annotation.png

Conclusion

As you can see, writing APIs with Swagger is extremely convenient. The solution enables declarative and consistent API documentation without laborious manual effort. As a result, it accelerates the development process and minimizes the risk of errors, so you should definitely integrate it into your workflow!

About

This project is a Spring Boot application that demonstrates API documentation using Springdoc OpenAPI.

Topics

Resources

Stars

Watchers

Forks

Languages