Documentar un API REST con Swagger y Spring Boot

La documentación de un API es casi tan importante como construirlos con buenas prácticas, pues su finalidad es que terceros la utilicen, por tal motivo, documentarla correctamente puede representar el éxito o fracaso en su implementación dentro de los sistemas de terceros:

Codigo fuente: Antes que nada, puedes descargar todo el código fuente en: https://github.com/codminddev/blog-spring-boot-swagger

La documentación de un API es casi tan importante como construirlos con buenas prácticas, pues su finalidad es que terceros la utilicen, por tal motivo, documentarla correctamente puede representar el éxito o fracaso en su implementación dentro de los sistemas de terceros:

Por suerte, Spring Boot cuenta con la librería de Swagger que permite analizar todo el proyecto y auto documentar todos los servicios REST que encuentre de forma automática. De esta forma, nos ahorramos una gran cantidad de trabajo de documentación. Por ejemplo, veamos el siguiente servicio:

@RestController
public class UserController {
	
	private List<UserDTO> users = new ArrayList<>();
	
	public UserController() {
		users.add(new UserDTO(1L, "admin"));
		users.add(new UserDTO(2L, "supervisor"));
		users.add(new UserDTO(3L, "cajero"));
	}

	@GetMapping(value = "users")
	public ResponseEntity<List<UserDTO>> findAll(){
		return ResponseEntity.ok(users); 
	}
	
	
	@PutMapping(value = "users")
	public ResponseEntity<UserDTO> update(UserDTO request){
		UserDTO user = users.stream()
				.filter(currentUser -> currentUser.getId() == request.getId())
				.findFirst()
				.orElseThrow(() -> new RuntimeException("No existe el usuario"));
		user.setName(request.getName());
		return ResponseEntity.ok(user);
	}
	
	@PostMapping(value = "users")
	public ResponseEntity<UserDTO> create(UserDTO request){
		users.add(request);
		return ResponseEntity.ok(request);
	}
	
	@DeleteMapping(value = "users/{userId}")
	public ResponseEntity<?> delete( @PathVariable("userId") long userId ) {
		UserDTO user = users.stream()
		.filter(currentUser -> currentUser.getId() == userId)
		.findFirst()
		.orElseThrow(() -> new RuntimeException("No existe el usuario"));
		users.remove(user);
		return ResponseEntity.ok().build();
		
	}
}

Podrá observar el clásico servicio CRUD de usuarios, donde tenemos un servicio para consultar todos los usuarios (GET), crear nuevos (POST), actualizarlos (PUT) y finalmente eliminarlos (DELETE).

Por suerte, Swagger es tan potente que puede aprovecharse del API de reflection para analizar la estructura de los métodos, sus anotaciones y los parámetros de entrada y salida para auto documentar el API, tal como se puede ver en la siguiente imagen:

Instalación

Para lograr esto, es requerido solamente dos pasos, agregar las librerías de Swagger y crear un objeto de configuración llamado Docket, veamos cómo sería:

Lo primero es agregar las siguientes librerías en el archivo pom.xml:

<dependency>
	<groupId>io.springfox</groupId>
	<artifactId>springfox-swagger2</artifactId>
	<version>2.9.2</version>
</dependency>

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

La primera (springfox-swagger2) es la más importante, pues es que se encarga de analizar la estructura de nuestro proyecto y crear los metadatos para crear la auto documentación del API. La segunda (springfox-swagger-ui) es que se encarga de tomar los metadatos de la primera y crear una interfaz gráfica amigable con los usuarios.

Configuración

El segundo paso es crear el archivo Docket, mediante el cual le indicamos a Swagger que es lo que debe de tomar en cuanta al momento de crear la documentación. Para esto, debemos de crear una clase que este anotada con @Configuration y @EnableSwagger2 y tenga un método que cree el objeto Docket:

@Configuration
@EnableSwagger2
public class SwaggerConfig {

	@Bean
	public Docket apiDocket() {
		return new Docket(DocumentationType.SWAGGER_2)
				.select()
				.apis(RequestHandlerSelectors.basePackage("com.codmind.swaggerapi.controllers"))
				.paths(PathSelectors.any())
				.build()
				.apiInfo(getApiInfo())
				;
	}
	
	private ApiInfo getApiInfo() {
		return new ApiInfo(
				"Order Service API",
				"Order Service API Description",
				"1.0",
				"http://codmind.com/terms",
				new Contact("Codmind", "https://codmind.com", "apis@codmind.com"),
				"LICENSE",
				"LICENSE URL",
				Collections.emptyList()
				);
	}
}

Dentro del método apiDocket construimos el objeto Docket, en el cual le indicamos que tome todos los servicios que se encuentre en el paquete com.codmind.swaggerapi.controllers y los auto documente por nosotros.

Finalmente, en el método getApiInfo, creamos el objeto ApiInfo, el cual define los datos de la propiedad del API, como su nombre, correo de contacto, licencia, etc.

La ejecución

Si todo salió bien y corremos la aplicación, veremos el siguiente log:

Podrás ver que nos ha generado un path llamado /v2/api-docs, por lo que si los accedemos en el navegador (http://localhost:8080/v2/api-docs) podremos ver los metadatos del API generado por la librería springfox-swagger2:

Sin embargo, esta documentación no es para nada intuitiva, y es por ello que hemos instalado la librería springfox-swagger-ui, la cual toma esta metadata y crea la URL: http://localhost:8080/swagger-ui.html, la cual se ve así:

En esta página podemos apreciar 3 secciones, la primera corresponde al objeto ApiInfo, que contiene nombre del API, licencia, datos de contacto, etc.

La segunda sección corresponde a los servicios, agrupados por controlador. En este caso, podemos apreciar el UserController (user-controller) con las 4 operaciones CRUD que hemos definido.

Si quieres aprender a crear un API REST profesional con Swagger, te invito a mi curso Mastering API REST, donde aprenderemos las mejores prácticas para crear un API REST.

Finalmente, tenemos la sección de Models (modelos), la cual describe todos los objetos que utilizamos como request/response.

Probando el API

Finalmente, si expandimos cualquier servicio, por ejemplo, el método create (POST) y presionamos el botón Try It Out, podremos probar el servicio allí mismo:

Nos desplegará los campos esperados por el servicio y si damos click en la sección Model, podrás ver la estructura de la respuesta esperada.

Conclusiones

Documentar solo este servicio, nos podría tomar varias horas, ya que es necesario documentar su nombre, los parámetros que recibe, los retorna y además, habría que describir cada unos de los objetos que recibe y responde, para que los consumidores tengan claridad de la estructura de cada uno de estos, pero como hemos podido comprobar, mediante Swagger nos evitamos todo este trabajo.

27 thoughts to “Documentar un API REST con Swagger y Spring Boot”

  1. No funciona sale un error
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.3.4.RELEASE.jar:2.3.4.RELEASE]
    Caused by: java.io.FileNotFoundException: class path resource [springfox/documentation/spring/web/SpringfoxWebConfiguration.class] cannot be opened because it does not exist
    at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:180) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
    at org.springframework.core.type.classreading.SimpleMetadataReader.getClassReader(SimpleMetadataReader.java:55) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
    at org.springframework.core.type.classreading.SimpleMetadataReader.(SimpleMetadataReader.java:49) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]
    at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[spring-core-5.2.9.RELEASE.jar:5.2.9.RELEASE]

  2. Una pregunta, ¿hay alguna posibilidad de cambiar la interfaz de swagger-ui? Me gustaría acceder al HTML que se muestra para cambiar, por ejemplo, la navigation bar.

    1. Hola Gerardo, esa es una buena pregunta, la verdad yo no he cambiado nada más allá del logo y el texto que aparece, pues al final creo que la idea es tener una auto documentación sin invertir tiempo en la creación de la página, pero sería cuestión de que entres a la documentación y ver hasta que punto es personalizable la UI.

  3. Y ¿que configuración se debe usar si ya tengo mis servicios configurados en un servidor Eureka accesibles a través de un gateway Zuul? Porque asi tal cual no son accesibles.

    Gracias

    1. Lo que se hace en estos casos es que debes crear interfaces en el proyecto de Zuul y anotarlas con Springfox (Swagger) para que solo se documenten los servicios expuestos por el Gateway, en lugar de exponer todos los servicios que tiene en el Backend

  4. Hola Oscar hay alguna chance o forma de poder utilizar Swagger para una api rest realizada con java 1.6?

    Saludos desde Argentina! Muy bueno tus aportes!!

  5. A mi me da error: —————-

    org.springframework.beans.factory.BeanDefinitionStoreException: Failed to process import candidates for configuration class [springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration]; nested exception is java.io.FileNotFoundException: class path resource [org/springframework/plugin/core/Plugin.class] cannot be opened because it does not exist

    ——————————————————————————————————-

    Caused by: java.io.FileNotFoundException: class path resource [org/springframework/plugin/core/Plugin.class] cannot be opened because it does not exist

    que podrá ser ?

  6. Hola Oscar, estas librerías son compatibles también con OpenAPI v3 o solo con OpenAPI v2? Si la respuesta es afirmativa para v3, qué cambios tendría tu implementación?
    Muchas gracias por tu aporte!

  7. org.springframework.context.ApplicationContextException: Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException
    Me aparece ese error, qué podrá ser?

  8. Hola, excelente post … una consulta ¿seria posible mostrar en la interfaz de swagger la versión del pom.xml del proyecto ? Es una info importante para saber en que versión de la API estamos… Gracias

  9. Buen tutorial, habra alguna manera de insertar un yaml personalizado, osea que no cree automaticamente la documentacion de los metodos definidos en el controller sino yo pueda manipularlos manualmente por un archivo yaml

    1. La idea de este API es que el código se auto-documente, de tal forma que no tengas que manipular un YAML manualmente. Personalmente no me he metido a ese nivel, por lo que no estoy seguro si eso sea una posibilidad mediante esta API. se que puedes hacerlo con herramientas tipo https://editor.swagger.io/, pero en esta en particular, no losé.

  10. Hola, gracias por la info una duda ¿Cómo se podrian agregar varias rutas es decir aparte de la com.codmind.swaggerapi.controllers buscar en otros paquetes?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *