Documentando un api con swagger
Puede que en alguna ocasión os haya sucedido que una ver terminado el desarrollo de un API os preguntéis ¿y ahora, cómo le explico al equipo la forma de usarla?, ¿les listo todos los métodos y parámetros en un mail?, ¿llamo por teléfono y explico cada endpoint y los datos que consume?. Para este tipo de situaciones disponemos de swagger, con esta herramienta vamos a conseguir crear una interfaz que por sí sola explicará nuestros métodos y nos permitirá probarlos ¿parece interesante, verdad? ¡ pues al lío!
- Primeros pasos
- Definición básica - Objeto OpenApi
- Components - Objetos reusables
- Schemas - Modelos de datos
- Parameters
- Paths y operaciones
- Responses
- RequestBody
- Autenticación y autorización
- Ejemplo completo
- Desplegar la documentación
Primeros pasos
Lo primero que tenemos que hacer es abrir la herramienta Swagger Editor y su api de ejemplo. En esta demo podemos observar un ejemplo de definición de API e ir familiarizándonos con los conceptos básicos de la misma. Esta herramienta está divida en dos, por un lado, un editor, donde en formato YAML iremos codificando la descripción de nuestro API, y por otro una imagen previa, donde observar el resultado final de nuestros ajustes. La edición de nuestro archivo YAML se basará en un conjunto de especificaciones y reglas denominado OpenApi.
También podemos descargarnos el editor de swagger en nuestro propio entorno. Una forma muy sencilla de desplegarlo es descarnos y ejecutar la imagen de docker
Después de esta breve introducción ha llegado el momento de editar nuestro archivo YAML y conocer los apartados del mismo. Comencemos por la raíz de nuestro documento:
Definición básica - Objeto OpenApi
El objeto OpenApi es la raíz de nuestro documento, que a su vez contiene campos básicos. Veamos un ejemplo de alguno de estos campos:
En este bloque podemos observar varios campos:
- openapi
- Definirá la versión de nuestra especificación de openapi utilizada.
- info
- Contendrá un objeto con información de utilidad sobre nuestro api.
- servers
- Array con las urls de los diferentes entornos donde está alojada nuestra API, por ejemplo:
- paths
- Rutas de nuestra aplicación
- components
- Componentes que reutilizaremos a lo largo de nuestro documento
- security
- Seguridad general aplicada a todos los endpoints de nuestro API
Components - Objetos reusables
En este apartado definiremos aquellos componentes que podremos reutilizar a lo largo de nuestro documento, como por ejemplo, los modelos de nuestra implementación. Podemos observar diferentes secciones.
Schemas
En esta sección definiremos las entidades utilizadas en nuestro API, es decir los datos que mostrará o consumirá esta misma. El objetivo es definir la estructura básica de los datos con los que trabajamos (podríamos decir que los modelos de base de datos). Una vez que los hayamos definido en esta sección, podremos asignarlos a las operaciones que los consuman o devuelvan.
En este bloque estamos definiendo un objeto User , que se corresponderá con nuestro módelo User en nuestro API. A continuación, enumero las propiedades utilizadas:
- required
- Array con los campos que son requeridos a la hora de crear un registro de nuestro modelo
- properties
- Campos del objeto
Cada campo a su vez, se describirá utilizando diferentes propiedades:
- type
- tipo de dato (integer, string, object)
- readOnly / writeOnly
- Indica si es un campo que solo se muestra cuando realizamos peticiones de consulta, pero no se necesita enviar cuando insertamos o actualizamos, o al revés.
- format
- Indica diferentes formatos para el tipo string (password, email, uuid, date)
- enum
- Listado de posibles valores que puede tomar la propiedad.
Normalmente los campos id suelen ser un candidato ideal para ser readOnly, ya que cuando creamos nuestro modelo, no necesitamos enviarlo, sino que se genera de forma automática.
Más adelante podremos ver cómo definir estos modelos en línea (sin necesidad de ser globales), no obstante como la mayoría se reutilizan en varios métodos de nuestro API, la forma más óptima es crearlos dentro del objeto global components.schemas.
Parameters
Los parámetros se pueden definir a nivel de operación o path y tambíen de forma global en el objeto components.parameter. Veamos varios ejemplos de esto:
Podemos observar varios atributos importantes:
- name
- Nombre del parámetro, es decir , el nombre con el que lo capturaremos.
- in
- Indicándonos la localización del parámetro (query, path, header, cookie) , en función de si el parámetro está presente en la url, como parámetro del path, en una cabecera o en una cookie .
- description
- Información descriptiva
- example
- Podemos indicar un valor de ejemplo de este parámetro
- schema.type
- Tipo de dato del parámetro
También podremos definirlos en la seccion parameters de la raíz de nuestro documento, haciéndolos de esta forma global al mismo y por ende reutilizables.
Algunos ejemplos de parámetros que son el candidato ideal para ser definidos de forma global, son aquellos para realizar paginación u ordenación, ya que irán incluidos en varias de nuestras peticiones.
Al establecer nuestros parámetros como globales, podremos reutilizarlos en varias operaciones, referenciándolos mediante $ref. Esto nos ahorrará mucho trabajo, evitando que tengamos que repetir la definición de los mismos.
Paths y operaciones
En OpenApi los paths son los endpoints, como puede ser /users o /users/:userId y las operaciones, los diferentes tipos de métodos http que les aplicamos (GET, POST, PUT, DELETE, etc..). A continuación veamos un ejemplo completo de la definición de varios path y operaciones:
En el anterior código podemos observar la definición de un endpoint /users y la operación get y post sobre si mismo.
Observemos cómo quedaría visualmente esta estructura:
Y una de nuestrás operaciones en detalle:
A continuación, echemos un vistazo a sus atributos:
- security
- En este apartado referenciamos un security scheme (que posteriormente definiremos en el apartado de autorización y autenticación).
- tags
- Las tags sirven para agrupar nuestras operaciones. Usaremos la tag 'user' y así todas las operaciones se visualizarán agrupadas en nuestra interfaz final.
- parameters
- Array de parameters específicos de esta operación . De igual modo también podemos referenciar a aquellos parámetros definidos como globales haciendo uso de $ref.
- responses
- Es necesario que definamos una respuesta por cada operación. Las respuestas se caracterizan por su http status code , el dato retornado y su content type.
No os preocupéis si no entendéis alguna de las propiedades expuestas en el anterior ejemplo, puesto que os las explicaré en los siguientes apartados.
Responses
Las respuestas se definen a nivel de operación y nos indican el código de estado http, el dato retornado y el tipo de contenido del mismo.
Observemos sus propiedades:
- código de estado
- Definido a través de un número (200, 404, 500, etc..)
- description
- Información descriptiva adicional.
- content
- Tipo de contenido, por ejemplo: application/json , application/xml, application/x-www-form-urlencoded, multipart/form-data, text/plain; charset=utf-8, text/html, etc...
- schema
- Aquí referenciaremos a nuestros modelos definidos anterioremente. También podremos definirlos en la propia definición de la response, pero al ser modelos que se reutlizarán en varias operaciones, lo correcto es definirlos de forma global.
RequestBody
Al igual que se definen las respuestas, también tendremos que definir la estructura de entrada de datos para aquellas operaciones que lo permitan, como las que usen PUT Y POST.
Podemos observar las siguientes propiedades:
- required
- Indicando la obligatoridad del dato.
- content
- Tipo de contenido, por ejemplo: application/json , application/xml, application/x-www-form-urlencoded, multipart/form-data, text/plain; charset=utf-8 ,text/html, etc...
- schema
- Aquí al igual que en las respuestas, referenciaremos a nuestros modelos definidos anterioremente.
Autenticación y autorización
OpenApi usa el término security schema para definir la autenticacíon y autorización. Los diferentes security schemes serán definidos en el objeto components.securitySchemes
Podemos observar las siguientes propiedades:
- type
- Puede tomar diferentes valores en función del tipo de autenticación ( http, apiKey, oauth2, openIdConnect).
- scheme
- Dentro de cada tipo de autenticación podemos elegir entre diferentes subtipos, por ejemplo, bearer y basic dentro de la autenticación http.
- bearerFormat
- Propiedad arbitraria que indica el tipo de token, en este caso JWT (JSON Web Token).
Una vez que hemos definidos nuestros esquemas de seguridad, podremos aplicarlos en todas las operaciones o sólo en determinados paths y operaciones , usando para esto la propiedad security de nuestro documento raíz o de nuestra operación.
Veamos un ejemplo de como enlazar un securityScheme para todas las rutas de nuestra API:
Ejemplo completo
Ahora que ya conocemos los componentes fundamentales en la estructura de un documento swagger, vamos a ver un ejemplo completo.
Desplegar la documentación
Ahora ya tenemos definido nuestro documento swagger, no obstante esto por sí solo, aún no nos sirve de nada. El siguiente paso es construir automáticamente una interfaz visual en base a él, en concreto, desplegaremos nuestra documentación sobre un API desarrollado en nodejs, bajo el framework Expressjs. Lo primero que tenemos que hacer es descargar el JSON con la definición de nuestro API del propio ditor de swagger, en el menú superior File –> “Convert and save as JSON”, una vez que tenemos este fichero, lo movemos a un directorio de nuestro proyecto. Para desplegar nuestra documentación swagger necesitamos añadir un nuevo módulo a nuestro API, el módulo swagger-ui-express, utilizando para ello el siguiente comando:
y añadimos las siguientes líneas de código a nuestro app.js
En este fragmento de código podemos ver cómo le indicamos que la ruta para consultar la documentación será /api-docs y que el fichero a buscar para comprender cómo construir la interfaz, será el swagger.json alojado en ese mismo directorio. Una vez hecho todo esto, ya podemos consultar la documentación swagger navegando a la ruta que le hemos indicado anteriormente.
Espero que este artículo os haya servido de ayuda y si tenéis cualquier duda, la resolveré sin problema 😉