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.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: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
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:openapi: 3.0.0
info:
description: Especificación de API para proyecto de ejemplo.
version: 1.0.0
title: API proyecto de ejemplo
servers:
- url: 'https://antoniofernandez.com'
paths:
#aquí nuestros paths
components:
#componentes reutilizables
security:
#seguridad
- 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:
servers: - url: 'https://antoniofernandez.com' description: 'Entorno de producción de apis' - url: 'https://development.antoniofernandez.com' description: 'Entorno de desarrollo de apis'
- 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.components:
parameters:
responses:
schemas:
securitySchemes:
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. schemas:
User:
type: object
required:
- email
- password
- role
properties:
id:
type: string
format: uuid
readOnly: true
username:
type: string
password:
type: string
format: password
email:
type: string
format: email
role:
type: string
enum:
- superadmin
- editor
- required
- Array con los campos que son requeridos a la hora de crear un registro de nuestro modelo
- properties
- Campos del objeto
- 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:
#definición a nivel de path
/users:
get:
summary: listar usuario
description: Este método lista usuarios
parameters:
- name: name
in: query
description: 'Buscar por nombre'
schema:
type: string
- 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
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.
parameters:
sortParam:
name: sort
in: query
description: "Realizar ordenacion"
example: "+fecha -nombre"
schema:
type: string
limitParam:
name: limit
in: query
description: "número de resultados"
example: 50
schema:
type: integer
skipParam:
name: skip
in: query
description: "número del que partir"
example: 50
schema:
type: integer
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: '/users':
get:
security:
- bearerAuth: []
tags:
- user
summary: 'Lista los usuarios del sistema buscando por nombre'
parameters:
- $ref: "#/components/parameters/sortParam"
- $ref: "#/components/parameters/limitParam"
- $ref: "#/components/parameters/skipParam"
- name: nombre
in: query
description: 'Nombre a buscar'
required: false
schema:
type: string
responses:
'200':
description: Operación correcta
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'422':
description: Error de validación
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Error de servidor
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
security:
- bearerAuth: []
tags:
- user
summary: 'Crea un nueo usuario'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'200':
description: Operación correcta
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'422':
description: Error de validación
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Error de servidor
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
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:- 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.
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. responses:
'200':
description: Operación correcta
'422':
description: Error de validación
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: Registro no encontrado
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Error de servidor
content:
application/json:
schema:
- 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. requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
- 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 securitySchemes:
basicAuth:
type: http
scheme: basic
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
- 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).
Veamos un ejemplo de como enlazar un securityScheme para todas las rutas de nuestra API: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.
openapi: 3.0.0
info:
description: Especificación de API para proyecto de ejemplo.
version: 1.0.0
title: API proyecto de ejemplo
servers:
- url: 'https://antoniofernandez.com'
security:
-bearerAuth []
Ejemplo completo
Ahora que ya conocemos los componentes fundamentales en la estructura de un documento swagger, vamos a ver un ejemplo completo.openapi: 3.0.0
info:
description: Ejemplo para blog.
version: 1.0.0
title: API de ejemplo para blog
servers:
- url: 'https://antoniofernandez.com'
description: 'Entorno de produccíon de apis'
- url: 'https://development.antoniofernandez.com'
description: 'Entorno de desarrollo de apis'
paths:
/users:
get:
security:
- bearerAuth: []
tags:
- user
summary: listar usuario
description: Este método lista usuarios
parameters:
- $ref: '#/components/parameters/sortParam'
- $ref: '#/components/parameters/limitParam'
- $ref: '#/components/parameters/skipParam'
- name: name
in: query
description: 'Filtro para buscar por nombre de usuario'
schema:
type: string
responses:
200:
description: operacion correcta
content:
application/json:
schema:
$ref: '#/components/schemas/User'
500:
description: error de servidor
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
security:
- bearerAuth: []
tags:
- user
summary: crear usuarios
description: Este método crea usuarios
responses:
200:
description: operacion correcta
content:
application/json:
schema:
$ref: '#/components/schemas/User'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
parameters:
sortParam:
name: sort
in: query
description: " ordenacion"
example: "+fecha -nombre"
schema:
type: string
limitParam:
name: limit
in: query
description: "número de resultados a obtener"
example: 50
schema:
type: integer
skipParam:
name: skip
in: query
description: "número de resultados desde el que partir"
example: 0
schema:
type: integer
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
User:
type: object
required:
- email
- password
- role
properties:
id:
type: string
format: uuid
readOnly: true
username:
type: string
password:
type: string
format: password
email:
type: string
format: email
role:
type: string
enum:
- superadmin
- editor
Error:
type: object
properties:
code:
description: Código de error
type: string
status:
description: httpstatus
type: integer
format: int32
type:
type: string
description: Tipo de error
message:
type: string
description: Mensaje de error
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:npm install swagger-ui-express
const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));