¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Fernando Jiménez 25/01/2024 Cargando comentarios…
En el desarrollo de aplicaciones frontend, la gestión de las API puede ser un desafío. La interacción con múltiples endpoints, la autenticación, el manejo de errores y la sincronización de los datos son solo algunos de los aspectos a considerar. Afortunadamente, existen herramientas como Orval que pueden simplificar en gran medida esta tarea. En este artículo, explicaremos qué es Orval y cómo puede ayudarte a mejorar la gestión de API en tus proyectos frontend.
Orval es una biblioteca de gestión de API para aplicaciones frontend. Está diseñada para facilitar la interacción con las API y reducir la complejidad asociada con la gestión de múltiples endpoints. Orval se integra fácilmente con frameworks populares como Vue, React y Angular, lo que la convierte en una opción versátil para proyectos frontend de diferentes tecnologías.
Para testear el funcionamiento de Orval, vamos a crear un proyecto de Vue, Vite y TypeScript, la configuración será similar si seleccionamos otro framework o librería como Angular o React.
npm create vite@latest orval # Seleccionaremos la opción de React y TypeScript
Para instalar la dependencia de Orval, ejecutaremos el siguiente comando:
npm install orval --save-dev
Una vez instalado Orval, tendremos que instalar axios, ya que será necesario para realizar las integraciones con los servicios correspondientes.
npm install axios
Esta será la configuración mínima en cuanto a dependencias, podemos añadir más funcionalidad a Orval dentro de nuestro proyecto, pero por ahora nos centraremos en la generación de interfaces y servicios.
Para configurar Orval en nuestro proyecto necesitaremos añadir un fichero orval.config.js a nuestro proyecto, en el directorio principal, o bien añadir la configuración mediante un parámetro --config una vez ejecutemos el comando. Personalmente, prefiero esta segunda opción, ya que así tendremos en el mismo directorio la configuración del servicio de Orval, las definiciones de OpenApi y las interfaces y servicios generados.
Para ello, tendremos de añadir un script a nuestro package.json similar al siguiente:
"generate:api": "orval --config src/api/config/orval.config.js"
En este ejemplo almacenaremos toda la configuración de Orval en la carpeta src/api.
El fichero de configuración debe de tener al menos un servicio definido, y podrá contener varios servicios que se definirán de la siguiente manera:
Nuestro fichero de configuración contendrá la siguiente información:
// orval.config.cjs
module.exports = {
pokemon: {
input: {
target: '../definitions/pokemon/pokemon.openapi.yaml',
},
output: {
target: '../generated/pokemon/pokemon.service.ts',
schemas: '../generated/pokemon/models',
},
},
};
Para generar las interfaces y servicio, necesitaremos la especificación OpenApi del servicio. En este ejemplo vamos a utilizar una especificación correspondiente al API REST de PokeApi, por lo que hemos generado el fichero de definición de OpenApi para los siguientes servicios:
Así como las interfaces correspondientes a los objetos de transferencia de información.
openapi: 3.0.1
info:
title: Pokemon Paradigma
description: Try to test some GET enpoints
version: 3.0.0
servers:
- url: 'https://pokeapi.co'
paths:
'/api/v2/pokemon':
get:
operationId: findAll
description: Find all pokemon list
summary: Finds all pokemon
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/PokemonListResponse'
'404':
description: Error
content:
text/plain; charset=utf-8:
schema:
type: string
examples: { }
servers:
- url: 'https://pokeapi.co'
servers:
- url: 'https://pokeapi.co'
'/api/v2/pokemon/{id}':
get:
operationId: findPokemonById
description: Enter id to find Pokemon
summary: Finds pokemon by Id
parameters:
- name: id
in: path
description: ID of region to return
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Pokemon'
'404':
description: Error
content:
text/plain; charset=utf-8:
schema:
type: string
examples: {}
servers:
- url: 'https://pokeapi.co'
servers:
- url: 'https://pokeapi.co'
'/api/v2/pokemon/{name}/':
get:
operationId: findPokemonByName
description: Enter name to find Pokemon Info
summary: Finds Pokemon by Name
parameters:
- name: name
in: path
description: The name that needs to pokemon info.
required: true
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Pokemon'
'404':
description: Error
content:
text/plain; charset=utf-8:
schema:
type: string
examples: {}
servers:
- url: 'https://pokeapi.co'
servers:
- url: 'https://pokeapi.co'
components:
schemas:
PokemonResponse:
title: PokemonResponse
type: object
properties:
name:
type: string
url:
type: string
PokemonListResponse:
title: PokemonListResponse
type: object
properties:
count:
type: number
next:
type: string
previous:
type: string
format: nullable
results:
type: array
items:
$ref: '#/components/schemas/PokemonResponse'
Pokemon:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
base_experience:
type: integer
format: int32
height:
type: string
is_default:
type: string
order:
type: string
abilities:
type: string
moves:
type: string
stats:
type: string
types:
type: string
xml:
name: Pokemon
Machine:
type: object
properties:
id:
type: integer
format: int64
item:
type: integer
format: int64
move:
type: integer
format: int32
version_group:
type: string
xml:
name: Machine
Region:
type: object
properties:
id:
type: integer
format: int64
locations:
type: integer
format: int64
move:
type: integer
format: int32
name:
type: string
names:
type: string
main_generation:
type: string
pokedexes:
type: string
version_group:
type: string
xml:
name: Region
Una vez configurado Orval dentro de nuestro proyecto, podremos ejecutar la generación de servicios e interfaces ejecutando el siguiente el comando:
npm run generate:api
Esto generará dentro de la ruta src/api/generated/pokemon los modelos y servicio de nuestra especificación de OpenApi.
Una vez hemos generado las interfaces y servicios podemos integrarlos en nuestro código, por ejemplo, crearemos un botón que ejecute la llamada al servicio de listado:
// components/HelloWorld.vue
import {findAll} from "../api/generated/pokemon/pokemon.service";
...
const getAll = () => {
console.log("get all");
findAll().then((result)=> {
console.log(result);
});
}
...
<button type="button" @click="getAll">Get all</button>
Una vez integrada la petición, si ejecutamos este código en nuestro navegador obtendremos el siguiente mensaje de error:
Esto se debe a que por defecto la propiedad baseUrl en axios será la url de nuestro servidor local, por tanto, tendremos que configurar la instancia de axios para asignar la url de nuestro servidor backend.
Para crear una instancia de axios, lo primero de todo tendremos que crear el archivo que la contendrá, por ejemplo axios.instance.ts. Siguiendo la documentación de Orval sobre la creación de instancias, el fichero deberá de contener el siguiente código:
// services/config/axios.instance.ts
import axios, { AxiosRequestConfig } from 'axios';
export const AXIOS_INSTANCE = axios.create({ baseURL: 'https://pokeapi.co' });
export const pokemonInstance = <T>(
config: AxiosRequestConfig,
options?: AxiosRequestConfig
): Promise<T> => {
const source = axios.CancelToken.source();
const promise = AXIOS_INSTANCE({
...config,
...options,
cancelToken: source.token,
}).then(({ data }) => data);
return promise;
}
Es recomendable parametrizar el atributo baseURL para generar la url mediante variables de entorno.
Una vez creado y configurado el fichero correspondiente tendremos que asignar la instancia de axios personalizada a nuestro servicio, esta asignación se realiza dentro del fichero de configuración de Orval, en el atributo output. Añadiendo el siguiente fragmento de código:
// api/config/orval.config.js
module.exports = {
pokemon: {
input: {
...
},
output: {
...
override: {
mutator: {
path: '../../services/config/axios.instance.ts',
name: 'pokemonInstance',
},
},
},
...
},
};
No hay que olvidar que el nombre de la constante que exportamos en axios.instance.ts, debe ser el mismo que el definido en override.mutator.name, en nuestro caso pokemonInstance. Por lo que si volvemos a generar las interfaces y servicios, las peticiones se harán correctamente a la url del servicio.
Una de las características de Orval es la posibilidad de añadir mocks a la hora de crear los servicios, puede ser una opción realmente útil para facilitar la integración con los servicios. Para ello, Orval se basa en la librería Mock Service Worker, que nos permitirá simular un Service Worker en entornos de desarrollo y pruebas.
Si quieres conocer cómo instalar y configurar correctamente Mock Service Worker en tu proyecto, visita el post simulando APIs para pruebas y desarrollo.
Una vez instalado y configurado MSW dentro de nuestro proyecto, procederemos a configurar Orval para generar los mocks junto con los servicios. Para ello, tendremos que modificar nuestro fichero de configuración orval.config.js y añadir la propiedad “mock” con el valor “true” dentro de output, obteniendo un bloque de código similar al siguiente:
output: {
target: '../generated/pokemon/pokemon.service.ts',
schemas: '../generated/pokemon/models',
mock: true,
override: {
mutator: {
path: '../../services/config/axios.instance.ts',
name: 'customInstance',
},
},
}
Una vez ejecutado nuevamente el script de generación, podremos observar cómo en nuestro fichero pokemon.service.ts se han generado cuatro nuevas exportaciones.
Estos mocks, por sí solos, no tienen ninguna funcionalidad. En el caso de Mock Service Worker, será necesario registrarlo en su fichero de configuración de la siguiente manera:
// mock.config.ts
import { RequestHandler } from 'msw';
import { setupWorker } from 'msw/browser';
import {getPokemonParadigmaMock} from "../../api/generated/pokemon/pokemon.service.ts";
const mocks: Array<RequestHandler> = [];
const loadHandler = () => {
// Añadir aquí los servicios mockeados
mocks.push(...getPokemonParadigmaMock());
};
const initMocks = () => {
loadHandler();
const worker = setupWorker(...mocks);
worker.start();
};
export default initMocks;
Orval dispone de una configuración de hooks propia para poder ejecutar tareas automatizadas una vez ha finalizado la generación de ficheros, por ejemplo aplicar las reglas de prettier de nuestro proyecto al código autogenerado.
Para ello tendremos que añadir el siguiente código a nuestro fichero de configuración:
module.exports = {
pokemon: {
input: {
...
},
output: {
...
},
hooks: {
afterAllFilesWrite: 'npx prettier --write ./src/api/generated',
},
},
};
De este modo al finalizar la generación se ejecutará el comando “npx prettier --write ./src/api/generated”, esto es solamente un ejemplo de tareas que podemos realizar con los hooks dentro de nuestro proyecto.
Orval es una biblioteca potente y versátil que simplifica la gestión de API en aplicaciones frontend. Proporciona una configuración sencilla, generación automática de clientes, gestión de autenticación, interceptores de peticiones y respuestas, y generación de documentación. Estas características hacen que Orval sea una herramienta valiosa para cualquier desarrollador frontend que desee optimizar el flujo de trabajo de las API y mejorar la eficiencia en el desarrollo de aplicaciones.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.