
Hoy tenemos un acoplamiento flojo entre el front-end y el back-end de las aplicaciones web. Por lo general, los desarrollan equipos separados, y mantener sincronizados a esos equipos y la tecnología no es fácil. Para resolver parte de este problema, podemos “falsificar” el servidor API que la tecnología de back-end normalmente crearía y desarrollaría como si la API o los puntos finales ya existieran.
El término más común utilizado para crear un componente simulado o “falso” es burlarse. La simulación le permite simular la API sin (idealmente) cambiar la interfaz. Hay muchas maneras de lograr burlarse, y esto es lo que hace que la mayoría de las personas lo asusten tanto, al menos en mi opinión.
Veamos cómo debería ser una buena simulación de API y cómo implementar una API simulada en una aplicación nueva o existente.
Tenga en cuenta que la implementación que estoy a punto de mostrar es independiente del marco, por lo que se puede usar con cualquier marco o aplicación de JavaScript estándar.
Mirage: El marco burlón
El enfoque de burla que vamos a usar se llama Mirage, que es algo nuevo. He probado muchos marcos de simulación y recientemente descubrí este, y ha sido un cambio de juego para mí.
Mirage se comercializa como un marco amigable para el front-end que viene con una interfaz moderna. Funciona en su navegador, del lado del cliente, al interceptar XMLHttpRequest
y Obtener solicitudes.
Pasaremos por la creación de una aplicación simple con una API simulada y cubriremos algunos problemas comunes en el camino.
configuración de espejismo
Vamos a crear una de esas aplicaciones de tareas estándar para demostrar la burla. Usaré Vue como mi marco de trabajo de elección pero, por supuesto, puede usar algo más ya que estamos trabajando con un enfoque agnóstico del marco.
Entonces, adelante e instale Mirage en su proyecto:
# Using npm
npm i miragejs -D
# Using Yarn
yarn add miragejs -D
Para comenzar a usar Mirage, necesitamos configurar un “servidor” (entre comillas, porque es un servidor falso). Antes de saltar a la configuración, cubriré la estructura de carpetas que encontré que funciona mejor.
/
├── public
├── src
│ ├── api
│ │ └── mock
│ │ ├── fixtures
│ │ │ └── get-tasks.js
│ │ └── index.js
│ └── main.js
├── package.json
└── package-lock.json
en un mock
directorio, abra un nuevo index.js
archivo y defina su servidor simulado:
// api/mock/index.js
import { Server } from 'miragejs';
export default function ({ environment="development" } = {}) {
return new Server({
environment,
routes() {
// We will add our routes here
},
});
}
El argumento del entorno que estamos agregando a la firma de la función es solo una convención. Podemos pasar en un entorno diferente según sea necesario.
Ahora, abra el archivo de arranque de su aplicación. En nuestro caso, este es él. src/main.js
archivo ya que estamos trabajando con Vue. Importa tu createServer
y llamarlo en el entorno de desarrollo.
// main.js
import createServer from './mock'
if (process.env.NODE_ENV === 'development') {
createServer();
}
estamos usando el process.env.NODE_ENV
variable de entorno aquí, que es una variable global común. El condicional permite que Mirage sea sacudido en árbol en producción, por lo tanto, no afectará su paquete de producción.
¡Eso es todo lo que necesitamos para configurar Mirage! Es este tipo de facilidad lo que hace que el DX de Mirage sea tan agradable.
Nuestra createServer
la función lo está por defecto a development
ambiente por el bien de hacer este artículo simple. En la mayoría de los casos, esto será por defecto test
ya que, en la mayoría de las aplicaciones, llamarás createServer
una vez en modo desarrollo pero muchas veces en archivos de prueba.
Cómo funciona
Antes de realizar nuestra primera solicitud, veamos rápidamente cómo funciona Mirage.
espejismo es un lado del cliente Marco de simulación, lo que significa que todas las burlas ocurrirán en el navegador, lo que Mirage hace usando la biblioteca Pretender. Pretender reemplazará temporalmente a nativo XMLHttpRequest
y Obtener configuraciones, interceptar todas las solicitudes y dirigirlas a un pequeño servicio ficticio al que se conecta el Mirage.
Si abre DevTools y se dirige a la pestaña Red, no verá ninguna solicitud de Mirage. Eso es porque la solicitud es interceptada y manejada por Mirage (a través de Pretender en el back-end). Mirage registra todas las solicitudes, a las que llegaremos en breve.
¡Hagamos pedidos!
Vamos a crear una solicitud a un /api/tasks
punto final que devolverá una lista de tareas que vamos a mostrar en nuestra aplicación de tareas pendientes. Tenga en cuenta que estoy usando axios para obtener los datos. Esa es solo mi preferencia personal. De nuevo, Mirage trabaja con nativos XMLHttpRequest
, Fetch y cualquier otra biblioteca.
// components/tasks.vue
export default {
async created() {
try {
const { data } = await axios.get('/api/tasks'); // Fetch the data
this.tasks = data.tasks;
} catch(e) {
console.error(e);
}
}
};
Al abrir su consola de JavaScript, debería haber un error de Mirage allí:
Mirage: Your app tried to GET '/api/tasks', but there was no route defined to handle this request.
Esto significa que Mirage se está ejecutando, pero el enrutador aún no se ha probado. Resolvamos esto agregando esa ruta.
Peticiones burlonas
Dentro de nuestro mock/index.js
archivo, hay un routes()
gancho. Los controladores de ruta nos permiten definir qué URL debe manejar el servidor de Mirage.
Para definir un controlador de enrutador, debemos agregarlo dentro del routes()
función.
// mock/index.js
export default function ({ environment="development" } = {}) {
// ...
routes() {
this.get('/api/tasks', () => ({
tasks: [
{ id: 1, text: "Feed the cat" },
{ id: 2, text: "Wash the dishes" },
//...
],
}))
},
});
}
El routes()
gancho es la forma en que definimos nuestros controladores de ruta. Usando un this.get()
el método nos permite burlarnos GET
peticiones. El primer argumento de todas las funciones de solicitud es la URL que estamos manejando, y el segundo argumento es una función que responde con algunos datos.
Como nota, Mirage acepta cualquier tipo de solicitud HTTP y cada tipo tiene la misma firma:
this.get('/tasks', (schema, request) => { ... });
this.post('/tasks', (schema, request) => { ... });
this.patch('/tasks/:id', (schema, request) => { ... });
this.put('/tasks/:id', (schema, request) => { ... });
this.del('/tasks/:id', (schema, request) => { ... });
this.options('/tasks', (schema, request) => { ... });
discutiremos el schema
y request
parámetros de la función de devolución de llamada en un momento.
Con esto, nos hemos burlado con éxito de nuestra ruta y deberíamos ver dentro de nuestra consola una respuesta exitosa de Mirage.
Trabajar con datos dinámicos
Intentar agregar una nueva tarea en nuestra aplicación no será posible porque nuestros datos en el GET
la respuesta tiene valores codificados. La solución de Mirage para esto es que brindan una capa de datos liviana que actúa como una base de datos. Arreglemos lo que tenemos hasta ahora.
Como el routes()
gancho, Mirage define un seeds()
gancho. Nos permite crear datos iniciales para el servidor. voy a mover el GET
datos a la seeds()
gancho donde lo enviaré a la base de datos de Mirage.
seeds(server) {
server.db.loadData({
tasks: [
{ id: 1, text: "Feed the cat" },
{ id: 2, text: "Wash the dishes" },
],
})
},
Moví nuestros datos estáticos de la GET
método para seeds()
gancho, donde esos datos se cargan en una base de datos falsa. Ahora, necesitamos refactorizar nuestro GET
para devolver datos de esa base de datos. En realidad, esto es bastante sencillo: el primer argumento de la función de devolución de llamada de cualquier route()
El método es el esquema.
this.get('/api/tasks', (schema) => {
return schema.db.tasks;
})
Ahora podemos agregar nuevos elementos pendientes a nuestra aplicación haciendo un POST
solicitud:
async addTask() {
const { data } = await axios.post('/api/tasks', { data: this.newTask });
this.tasks.push(data);
this.newTask = {};
},
Nos burlamos de esta ruta en Mirage creando un POST /api/tasks
controlador de ruta:
this.post('/tasks', (schema, request) => {})
Usando el segundo parámetro de la función de devolución de llamada, podemos ver la solicitud enviada.
Dentro de requestBody
property son los datos que enviamos. Eso significa que ahora está disponible para que podamos crear una nueva tarea.
this.post('/api/tasks', (schema, request) => {
// Take the send data from axios.
const task = JSON.parse(request.requestBody).data
return schema.db.tasks.insert(task)
})
El id
de la tarea será establecida por la base de datos de Mirage de forma predeterminada. Por lo tanto, no es necesario realizar un seguimiento de las identificaciones y enviarlas con su solicitud, como un servidor real.
¿Rutas dinámicas? ¡Seguro!
Lo último que hay que cubrir son las rutas dinámicas. Nos permiten usar un segmento dinámico en nuestra URL, que es útil para eliminar o actualizar un solo elemento de tarea pendiente en nuestra aplicación.
Nuestra solicitud de eliminación debe ir a /api/tasks/1
, /api/tasks/2
, y así. Mirage nos proporciona una manera de definir un segmento dinámico en la URL, como esta:
this.delete('/api/tasks/:id', (schema, request) => {
// Return the ID from URL.
const id = request.params.id;
return schema.db.tasks.remove(id);
})
Usando dos puntos (:
) en la URL es cómo definimos un segmento dinámico en nuestra URL. Después de los dos puntos, especificamos el nombre del segmento que, en nuestro caso, se llama id
y se asigna a la identificación de un elemento de tarea específico. Podemos acceder al valor del segmento a través de la request.params
objeto, donde el nombre de la propiedad corresponde al nombre del segmento — request.params.id
. Luego usamos el esquema para eliminar un elemento con esa misma ID de la base de datos de Mirage.
Si te has dado cuenta, todas mis rutas hasta ahora tienen el prefijo api/
. Escribir esto una y otra vez puede ser engorroso y es posible que desee hacerlo más fácil. Mirage ofrece la namespace
propiedad que puede ayudar. Dentro del gancho de rutas, podemos definir el namespace
propiedad para que no tengamos que escribir eso cada vez.
routes() {
// Prefix for all routes.
this.namespace="/api";
this.get('/tasks', () => { ... })
this.delete('/tasks/:id', () => { ... })
this.post('/tasks', () => { ... })
}
Bien, integremos esto en una aplicación existente
Hasta ahora, todo lo que hemos visto integra Mirage en una nueva aplicación. Pero, ¿qué hay de agregar Mirage a una aplicación existente? Mirage lo tiene cubierto para que no tenga que simular toda su API.
Lo primero que debe tener en cuenta es que agregar Mirage a una aplicación existente generará un error si el sitio realiza una solicitud que Mirage no maneja. Para evitar esto, podemos decirle a Mirage que pase por todas las solicitudes no gestionadas.
routes() {
this.get('/tasks', () => { ... })
// Pass through all unhandled requests.
this.passthrough()
}
Ahora podemos desarrollar sobre una API existente con Mirage manejando solo las partes faltantes de nuestra API.
Mirage puede incluso cambiar la URL base de la que captura las solicitudes. Esto es útil porque, por lo general, un servidor no vivirá en localhost:3000
sino más bien en un dominio personalizado.
routes() {
// Set the base route.
this.urlPrefix = 'https://devenv.ourapp.example';
this.get('/tasks', () => { ... })
}
Ahora, todas nuestras solicitudes apuntarán al servidor API real, pero Mirage las interceptará como lo hizo cuando lo configuramos con una nueva aplicación. Esto significa que la transición de Mirage a la API real es bastante fluida: elimine la ruta del servidor simulado y todo estará listo.
Terminando
En el transcurso de cinco años, he usado muchos marcos burlones, pero nunca me gustó ninguna de las soluciones que existen. Eso fue hasta hace poco, cuando mi equipo se enfrentó a la necesidad de una solución simulada y me enteré de Mirage.
Otras soluciones, como el servidor JSON de uso común, son procesos externos que deben ejecutarse junto con el front-end. Además, a menudo no son más que un servidor Express con funciones de utilidad en la parte superior. El resultado es que los desarrolladores front-end como nosotros necesitamos saber sobre el middleware, NodeJS y cómo funcionan los servidores… cosas que muchos de nosotros probablemente no queremos manejar. Otros intentos, como Mockoon, tienen una interfaz compleja y carecen de funciones muy necesarias. Hay otro grupo de marcos que solo se usan para pruebas, como el popular SinonJS. Desafortunadamente, estos marcos no se pueden usar para simular el comportamiento normal.
Mi equipo logró crear un servidor funcional que nos permite escribir código front-end como si estuviéramos trabajando con un back-end real. Lo hicimos escribiendo el código base de front-end sin ningún proceso externo o servidor que se necesita para ejecutar. Por eso me encanta Mirage. Es realmente simple de configurar, pero lo suficientemente potente como para manejar cualquier cosa que se le presente. Puede usarlo para aplicaciones básicas que devuelven una matriz estática a aplicaciones de back-end completas por igual, independientemente de si se trata de una aplicación nueva o existente.
Hay mucho más en Mirage más allá de las implementaciones que cubrimos aquí. Puede encontrar un ejemplo funcional de lo que cubrimos en GitHub. (Dato curioso: ¡Mirage también funciona con GraphQL!) Mirage tiene una documentación bien escrita que incluye un montón de tutoriales paso a paso, así que asegúrese de revisarla.