Как документировать REST API с помощью Swagger в Spring Boot 3
Документирование RESTful API (Application Programming Interface) — неотъемлемая часть создания веб-приложений. На смену ручному документированию API пришли инструменты, которые решают эту задачу быстрее.
Из этой статьи вы узнаете, как внедрить Swagger, основанный на спецификации OpenAPI 3.0, в проект на Spring Boot.
Содержание:
- Что такое Swagger?
- Внедрение Swagger в проект на Spring Boot
- Расширенная настройка Swagger в Spring Boot 3 с помощью аннотаций
- Добавление описания Swagger API
- Валидация бина (Bean)
- @Tag
- @Operation
- @ApiResponses
- @Parameter
- Заключение
Что такое Swagger?
Swagger — набор инструментов для создания, редактирования, кодогенерации и использования API-документации в соответствии со спецификацией OpenAPI. С появлением Swagger отпала необходимость писать длинную API-документацию вручную. Swagger умеет читать структуру API, определённую с помощью аннотаций в коде, и генерировать из неё спецификацию API.
У Swagger также есть UI, который показывает интерактивную API-документацию, как в примере Petstore. Этот UI также позволяет тестировать API, выполняя запросы в браузере.
Ключевые компоненты Swagger:
- Swagger Editor для написания и редактирования API-спецификации,
- Swagger UI для создания интерактивной API-документации,
- Swagger Codegen для генерации серверного макета или клиентской библиотеки для вашего API.
В контексте API-документации термины Swagger и OpenAPI часто идут вместе, но это не синонимы. OpenAPI — спецификация для описания API, а Swagger — фреймворк для создания API-документации в соответствии с этой спецификацией.
Внедрение Swagger в проект на Spring Boot
После прочтения этого раздела вы создадите своё небольшое CRUD-приложение, сгенерируете к нему API-документацию и увидите её в Swagger UI.
Предварительные требования:
- JDK 17 или новее (мы используем Axiom JDK),
- Maven,
- Docker,
- любая IDE.
1. Создайте проект REST API
Если у вас уже есть готовый проект, то пропустите этот шаг. Вы можете идти по руководству или использовать его отдельные примеры в своём проекте.
Код демо-проекта доступен на GitHub.
У нашего демо-приложения на Spring Boot будет REST API для управления сотрудниками. У сотрудника есть:
- идентификационный номер (
id
), - имя (
firstName
), - фамилия (
lastName
).
Вот таблица с методами API, которые мы реализуем:
Метод | Эндпоинт | Описание |
---|---|---|
GET | /v1/employees | Получить список всех сотрудников |
GET | /v1/employees/{employeeID} | Получить данные о конкретном сотруднике |
POST | /v1/employees | Добавить сотрудника |
PUT | /v1/employees/ | Обновить данные о сотруднике |
DELETE | /v1/employees/{employeeID} | Удалить сотрудника |
Чтобы сэкономить время и силы и не создавать локальную БД, мы будем использовать Testcontainers, которые обеспечивают подключение к готовым образам баз данных в Docker.
Чтобы создать основу проекта, сделайте следующее:
1) Откройте Spring Initializr.
- В разделе Language выберите Java,
- Project — Maven,
- Project Metadata -> Artifact — openapidemo (или любое другое имя на ваш выбор),
- Project Metadata -> Name — имя проекта (у нас это OpenapiSpringDemo),
- Packaging — Jar,
- Java — 17 или ту, с которой вы работаете.
В некоторых IDE, например в IntelliJ IDEA Ultimate, Spring Initializr доступен в виде плагина. Это значит, что вам не нужно скачивать архив проекта, распаковывать его и импортировать в IDE. Достаточно просто настроить всё, что нужно, не выходя из IDE.
2) Добавьте следующие зависимости, нажав ADD DEPENDENCIES…
- Spring Web,
- Lombok,
- Spring Data JDBC,
- PostgreSQL Driver,
- Testcontainers,
- Jakarta Persistence API,
- Spring Boot DevTools. DevTools экономит время привнесении изменений в коде. Вам не нужно перезапускатьприложение каждый раз, когда вы вносите изменения,перекомпиляция выполняется “на лету”.
3) Сгенерируйте проект, нажав GENERATE. У вас скачается файл demo.zip. Разархивируйте его и импортируйте проект в свою IDE.
Структура проекта будет простая:
- Класс
Employee
, который будет предоставлять информацию о сотрудниках. - Контроллер
EmployeeController
, который будет предоставлять RESTful API для работы с объектами класса Employee. - Интерфейс
EmployeeRepository
для работы с объектами класса Employee и выполнения базовых операций CRUD (Create, Read, Update, Delete). - Класс
EmployeeNotFoundException
для обработки ошибок.
Employee.java
package com.example.openapidemo;
import jakarta.persistence.GenerationType;
import jakarta.validation.constraints.NotNull;
import jakarta.persistence.GeneratedValue;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
@Getter
@Setter
@RequiredArgsConstructor
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotNull
private String firstName;
@NotNull
private String lastName;
}
EmployeeRepository.java
package com.example.openapidemo;
import org.springframework.data.repository.ListCrudRepository;
public interface EmployeeRepository extends ListCrudRepository<Employee, Integer> { }
EmployeeController.java
package com.example.openapidemo;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import java.util.List;
@RestController
@AllArgsConstructor
@RequestMapping("/v1")
public class EmployeeController {
private EmployeeRepository repository;
@GetMapping("/employees")
public List<Employee> findAllEmployees() {
return repository.findAll();
}
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@PathVariable int employeeId) {
return repository.findById(employeeId)
.orElseThrow(() -> new EmployeeNotFoundException(employeeId));
}
@PostMapping("/employees")
public ResponseEntity<Employee> addEmployee(@RequestBody Employee newEmployee) {
Employee savedEmployee = repository.save(newEmployee);
return ResponseEntity.status(HttpStatus.CREATED).body(savedEmployee);
}
@PutMapping("/employees/")
public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
return repository.findById(employee.getId())
.map(existingEmployee -> {
existingEmployee.setFirstName(employee.getFirstName());
existingEmployee.setLastName(employee.getLastName());
Employee updatedEmployee = repository.save(existingEmployee);
return ResponseEntity.ok(updatedEmployee);
})
.orElseGet(() -> ResponseEntity.notFound().build());
}
@DeleteMapping("/employees/{employeeId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public ResponseEntity<Void> deleteEmployee(@PathVariable int employeeId) {
repository.findById(employeeId).ifPresentOrElse(
employee -> repository.deleteById(employeeId),
() -> {
throw new EmployeeNotFoundException(employeeId);
}
);
return ResponseEntity.noContent().build();
}
}
EmployeeNotFoundException.java
package com.example.openapidemo;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class EmployeeNotFoundException extends RuntimeException {
public EmployeeNotFoundException (int employeeId) {
super("Сотрудник с ID " + employeeId + " не найден");
}
}
4) В файл pom.xml
добавьте зависимость для Jakarta Persistence API:
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
5) В папке test
откройте класс TestOpenapiSpringDemoApplication
(имя вашего класса может отличаться). Добавьте аннотацию @RestartScope
в метод PostgreSQLContainer<?> postgresContainer()
. Так сохранится бин. БД не будет перезапускаться каждый раз с перезапуском приложения.
6) Предоставьте схему базы данных. Для этого в директории resources создайте файл schema.sql
со следующим содержимым:
CREATE TABLE IF NOT EXISTS employee (
id SERIAL PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL
);
7) (Необязательно) Добавьте в БД несколько записей, чтобы не видеть страницу ошибки в браузере при запуске приложения. Для разработки API это не нужно. В той же директории resources
создайте файл data.sql
:
TRUNCATE employee;
INSERT INTO employee (first_name, last_name)
VALUES ('Иван', 'Иванов'),
('Петр', 'Петров');
8) Установите режим инициализации SQL, добавив в файл aplication.properties
следующую строку:
spring.sql.init.mode=always
Это нужно, потому что мы инициализируем БД с помощью SQL-скрипта.
9) Проверьте, что всё работает как ожидается. Для этого запустите на своей машине Docker и запустите класс TestOpenapiSpringDemoApplication
. Spring Boot подтянет образ базы данных PostgreSQL за вас.
В браузере откройте http://localhost:8080/v1/employees
. Вы должны увидеть что-то подобное:
[
{
"id": 1,
"firstName": "Иван",
"lastName": "Иванов"
},
{
"id": 2,
"firstName": "Петр",
"lastName": "Петров"
}
]
Простое CRUD-приложение готово для экспериментов.
2. Добавьте зависимость для springdoc-openapi
Для работы с Swagger вам нужна библиотека springdoc-api
. Эта библиотека нужна для генерации API-документации, совместимой с спецификацией OpenAPI, для проектов на Spring Boot. springdoc-api
поддерживает Swagger UI и другие инструменты, такие как OAuth2 и GraalVM Native Image.
В файл pom.xml
добавьте зависимость для springdoc-api
:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
Никаких дополнительных настроек не требуется.
3. Сгенерируйте API-документацию
Документация OpenAPI генерируется при сборке проекта. Убедитесь, что всё работает корректно. Для этого запустите ваш проект и перейдите на страницу API-документации по умолчанию: http://localhost:8080/v3/api-docs
.
Чтобы выложить документацию в продакшен, замените localhost:8080
на публичный адрес.
Вы увидите эндпоинты (endpoints) и данные по ним в формате JSON:
{
"openapi": "3.0.1",
"info": {
"title": "OpenAPI definition",
"version": "1.0"
},
"servers": [
{
"url": "http://localhost:8080",
"description": "Generated server url"
}
],
"paths": {
"/employees": {
"get": {
"tags": [
"employee-controller"
],
"operationId": "findAllEmployees",
"responses": {
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Employee"
}
}
}
}
}
}
},
...
Вы также можете открыть YAML-документ с теми же данными по следующей ссылке: http://localhost:8080/v3/api-docs.yaml
.
При необходимости вы можете изменить путь по умолчанию до API-документации в файле application.properties
. Например:
springdoc.api-docs.path=/api-docs
Теперь документация доступна по ссылке: http://localhost:8080/api-docs
.
4. Откройте Swagger UI
Поскольку библиотека springdoc-openapi
уже поддерживает Swagger UI, вам не нужно настраивать этот инструмент отдельно.
По ссылке http://localhost:8080/swagger-ui/index.html
вы увидите интерфейс для взаимодействия с эндпоинтами:
Все эндпоинты можно раскрыть и посмотреть как будет выглядеть API-запрос и ответ на него. Для этого нажмите Try it out и Execute.
Расширенная настройка Swagger в Spring Boot 3 с помощью аннотаций
Текущая API-документация не сильно информативна. Её можно дополнить с помощью аннотаций в коде приложения. Ниже приведены самые популярные аннотации. Список всех аннотаций можно посмотреть в вики Swagger.
Добавление описания Swagger API
Добавьте в API-документацию имя и контакты автора проекта, а также описание, для чего нужен этот API. Для этого создайте класс OpenAPIConfiguration
:
Из соображений безопасности приложения переопределите информацию о URL сервера и номере порта в application.properties
:
api.server.url=http://localhost:8080
package com.example.openapidemo;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import java.util.List;
@Configuration
@AllArgsConstructor
public class OpenAPIConfiguration {
private Environment environment;
@Bean
public OpenAPI defineOpenAPI () {
Server server = new Server();
String serverUrl = environment.getProperty("api.server.url");
server.setUrl(serverUrl);
server.setDescription("Development");
Contact myContact = new Contact();
myContact.setName("Имя Фамилия");
myContact.setEmail("my.email@example.com");
Info info = new Info()
.title("Системное API для управления сотрудниками")
.version("1.0")
.description("Это API предоставляет эндпоинты для управления сотрудниками.")
.contact(myContact);
return new OpenAPI().info(info).servers(List.of(server));
}
}
Вы также можете добавить информацию о лицензии и др. Кода выше достаточно для того, чтобы посмотреть, как это будет выглядеть в документации. Запустите проект, откройте страницу с API-документацией по ссылке http://localhost:8080/swagger-ui/index
.html и убедитесь, что введённые вами данные отображаются:
Валидация бина (Bean)
Библиотека springdoc-openapi
поддерживает стандарт JSR 303: Bean Validation (например, @NotNull
, @Min
, @Max
и @Size
) для валидации входных данных. Добавьте эти аннотации в код проекта, и у вас автоматически сгенерируется схема в разделе Schemas Swagger UI.
Определите аннотации в классе Employee
:
package com.example.openapidemo;
import jakarta.persistence.GenerationType;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import jakarta.persistence.GeneratedValue;
@Getter
@Setter
@RequiredArgsConstructor
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@NotNull
@Size(min = 1, max = 50)
private String firstName;
@NotNull
@Size(min = 1, max = 70)
private String lastName;
}
Перекомпилируйте проект и откройте документацию. Вы увидите, что раздел Schemas обновился:
@Tag
С помощью @Tag
можно группировать классы и методы API.
Вот как можно использовать эту аннотацию для группировки GET-методов в файле контроллера EmployeeController
:
import io.swagger.v3.oas.annotations.tags.Tag;
@Tag(name = "get", description = "GET-методы Employee API")
@GetMapping("/employees")
public List<Employee> findAllEmployees() {
return repository.findAll();
}
@Tag(name = "get", description = "GET-методы Employee API")
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@Parameter(
description = "ID сотрудника, данные по которому запрашиваются",
required = true)
@PathVariable int employeeId) {
return repository.findById(employeeId)
.orElseThrow(() -> new EmployeeNotFoundException(employeeId));
}
Когда вы откроете документацию, то увидите, что группировка методов изменилась: GET-методы теперь сгруппированы отдельно от остальных.
@Operation
С помощью @Operation
можно добавить краткое описание (summary
) и расширенное описание (description
) метода API.
Рассмотрим применение @Operation
для описания метода updateEmployee()
в том же файле контроллера EmployeeController
:
import io.swagger.v3.oas.annotations.Operation;
@Operation(summary = "Обновить данные о сотруднике", description = "В ответе возвращается объект Employee c полями id, firstName и lastName.")
@PutMapping("/employees/")
public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
return repository.findById(employee.getId())
.map(existingEmployee -> {
existingEmployee.setFirstName(employee.getFirstName());
existingEmployee.setLastName(employee.getLastName());
Employee updatedEmployee = repository.save(existingEmployee);
return ResponseEntity.ok(updatedEmployee);
})
.orElseGet(() -> ResponseEntity.notFound().build());
}
Описание API-метода в Swagger UI будет выглядеть так:
@ApiResponses
@ApiResponses
даёт описание ответа для метода. Каждый ответ помечается тегом @ApiResponse
:
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ApiResponses({
@ApiResponse(responseCode = "204", description = "Сотрудник успешно удалён"),
@ApiResponse(responseCode = "404", description = "Сотрудник не найден")
})
@DeleteMapping("/employees/{employeeId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public ResponseEntity<Void> deleteEmployee(@PathVariable int employeeId) {
repository.findById(employeeId).ifPresentOrElse(
employee -> repository.deleteById(employeeId),
() -> {
throw new EmployeeNotFoundException(employeeId);
}
);
return ResponseEntity.noContent().build();
}
После перекомпиляции класса EmployeeController
, описание API-ответа обновится:
@Parameter
Аннотация @Parameter
используется для параметра API-метода и описывает этот параметр операции.
Рассмотрим на примере метода getEmployee()
:
import io.swagger.v3.oas.annotations.Parameter;
@GetMapping("/employees/{employeeId}")
public Employee getEmployee(@Parameter(
description = "ID сотрудника, данные по которому запрашиваются",
required = true)
@PathVariable int employeeId) {
return repository.findById(employeeId)
.orElseThrow(() -> new EmployeeNotFoundException(employeeId));
}
Атрибут description
даёт дополнительную информацию о назначении параметра, а required = true
показывает, что параметр обязателен для заполнения.
В Swagger UI вы увидите следующее:
Заключение
Документирование API c помощью Swagger удобно. Такое решение позволяет генерировать полную и единообразную документацию к API без ручного труда. Это ускоряет разработку и сводит к минимуму риски ошибок.
Приложения на Spring Boot разворачиваются в контейнерах. Для этого отлично подходит Axiom Runtime Container Pro. Это такая же хорошая практика, как и документирование API.