Un tutorial completo de las API GraphQL con React y FaunaDB
- Nuestra aplicación
- Creando la aplicación React
- Implementación en Heroku
- Aprovisionamiento de una base de datos FaunaDB
- Creando una API a través del esquema GraphQL en FaunaDB
- Aprovisionamiento de una nueva clave de base de datos
- Conexión a FaunaDB en React con Apollo
- Agregar nuevas tareas pendientes
- Eliminar nuevas tareas pendientes
- Marcar elementos como completos
- Conclusión
Como desarrollador web, hay un intercambio interesante que siempre viene junto con la configuración de una nueva aplicación. Incluso usar un marco web de pila completa como Ruby on Rails puede no ser trivial de configurar e implementar, especialmente si es la primera vez que lo hace en mucho tiempo.
Personalmente, siempre me ha gustado más poder profundizar y escribir la parte real de la lógica de la aplicación que configurar las aplicaciones en sí. Últimamente me he convertido en un gran admirador de las aplicaciones React junto con una API GraphQL y el almacenamiento de estado con la biblioteca Apollo.
Configurar una aplicación React se ha vuelto muy fácil en los últimos años, pero ¿configurar un backend con una API GraphQL? No tanto. Entonces, cuando estaba trabajando en un proyecto recientemente, decidí buscar una forma más fácil de integrar una API GraphQL y estuve encantado de encontrar FaunaDB.
FaunaDB es una base de datos NoSQL como servicio que hace que el aprovisionamiento de una API GraphQL sea un proceso increíblemente simple e incluso viene con un nivel gratuito. Francamente, me sorprendió y realmente me impresionó lo rápido que pude pasar de cero a una API funcional.
El servicio también promociona su preparación para la producción, centrándose en hacer que la escalabilidad sea mucho más fácil que si estuviera administrando su propio backend. Aunque todavía no he explorado sus características más avanzadas, si se parece en algo a mi primera impresión, entonces las perspectivas e implicaciones de usar FaunaDB son bastante interesantes. Por ahora, puedo confirmar que para muchos de mis proyectos proporciona una excelente solución para gestionar el estado junto con una aplicación React.
Mientras trabajaba en mi proyecto, me encontré con algunos problemas de configuración al hacer que todos los marcos funcionaran juntos, lo cual creo que se podría haber solucionado con una guía que se centra en explicar cómo defender una aplicación en su totalidad. Entonces, en este artículo, haré un tutorial completo sobre cómo configurar una pequeña aplicación React para tareas pendientes en Heroku y luego persistir los datos en esa aplicación con FaunaDB usando la biblioteca Apollo. Puede encontrar el código fuente completo aquí.
Nuestra aplicación
Para este tutorial, estamos creando una lista de tareas pendientes donde un usuario puede realizar las siguientes acciones:
- Agregar un nuevo elemento
- Marcar un elemento como completo
- Eliminar un artículo
Desde una perspectiva técnica, vamos a lograr esto haciendo lo siguiente:
- Creando una aplicación React
- Implementación de la aplicación en Heroku
- Aprovisionamiento de una nueva base de datos FaunaDB
- Declarar un esquema de API GraphQL
- Aprovisionamiento de una nueva clave de base de datos
- Configurando Apollo en nuestra aplicación React para interactuar con nuestra API
- Escribir lógica de aplicación y consumir nuestra API para conservar la información.
Aquí hay una vista previa de cómo se verá el resultado final:
Creando la aplicación React
Primero, crearemos una aplicación React estándar y nos aseguraremos de que se ejecute. Suponiendo que tenga instalado create-react-app, los comandos para crear una nueva aplicación son:
create-react-app fauna-todocd fauna-todoyarn start
Después de lo cual debería poder dirigirse http://localhost:3000
y ver la página de inicio generada para la aplicación.
Implementación en Heroku
Como mencioné anteriormente, implementar aplicaciones React se ha vuelto increíblemente fácil en los últimos años. Estoy usando Heroku aquí ya que ha sido mi plataforma de referencia como servicio desde hace un tiempo, pero también podrías usar otro servicio como Netlify (aunque, por supuesto, la configuración será ligeramente diferente). Suponiendo que tiene una cuenta de Heroku y la CLI de Heroku instalada, este artículo muestra que solo necesita unas pocas líneas de código para crear e implementar una aplicación React.
git initheroku create -b https://github.com/mars/create-react-app-buildpack.gitgit push heroku master
¡Y tu aplicación está implementada! Para verlo puedes ejecutar:
heroku open
Aprovisionamiento de una base de datos FaunaDB
Ahora que tenemos una aplicación React en funcionamiento, agreguemos persistencia a la aplicación usando FaunaDB. Dirígete a fauna.com para crear una cuenta gratuita. Después de tener una cuenta, haga clic en "Nueva base de datos" en el panel e ingrese el nombre que elija:
Creando una API a través del esquema GraphQL en FaunaDB
En este ejemplo, declararemos un esquema GraphQL y luego usaremos ese archivo para generar automáticamente nuestra API dentro de FaunaDB. Como primer intento del esquema de nuestra aplicación de tareas pendientes, supongamos que simplemente hay una colección de "Elementos" con "nombre" como único campo. Como planeo desarrollar este esquema más adelante y me gusta poder ver el esquema en sí de un vistazo, crearé un schema.graphql
archivo y lo agregaré al nivel superior de mi aplicación React. Aquí está el contenido de este archivo:
type Item { name: String}type Query { allItems: [Item!]}
Si no está familiarizado con el concepto de definir un esquema GraphQL, considérelo como un manifiesto para declarar qué tipos de objetos y consultas están disponibles dentro de su API. En este caso, estamos diciendo que habrá un Item
tipo con un name
campo de cadena y que tendremos una consulta explícita allItems
para buscar todos los registros de artículos. Puede leer más sobre esquemas en este artículo de Apollo y tipos en este artículo de Graphql.org. FaunaDB también proporciona un documento de referencia para declarar e importar un archivo de esquema.
Ahora podemos cargar este schema.graphql
archivo y usarlo para generar una API GraphQL. Dirígete al panel de control de FaunaDB nuevamente y haz clic en "GraphQL", luego carga tu archivo de esquema recién creado aquí:
¡Felicidades! Ha creado una API GraphQL completamente funcional. Esta página se convierte en un "Área de juegos GraphQL" que le permite interactuar con su API. Haga clic en la pestaña "Documentos" en la barra lateral para ver las consultas y mutaciones disponibles.
Tenga en cuenta que además de nuestra allItems
consulta, FaunaDB ha generado las siguientes consultas/mutaciones automáticamente en nuestro nombre:
findItemByID
createItem
updateItem
deleteItem
Todos estos se derivaron declarando el Item
tipo en nuestro archivo de esquema. Bastante genial, ¿verdad? Demos una vuelta a estas consultas y mutaciones para familiarizarnos con ellas. Podemos ejecutar consultas y mutaciones directamente en el "GraphQL Playground". Primero ejecutemos una consulta de artículos. Ingrese esta consulta en el panel izquierdo del patio de juegos:
query MyItemQuery { allItems { data { name } }}
Luego haga clic en el botón de reproducción para ejecutarlo:
El resultado aparece en el panel derecho y, como era de esperar, no arroja resultados ya que aún no hemos creado ningún elemento. Afortunadamente, createItem
una de las mutaciones se generó automáticamente a partir del esquema y podemos usarla para completar un elemento de muestra. Ejecutemos esta mutación:
mutation MyItemCreation { createItem(data: { name: "My first todo item" }) { name }}
Puede ver el resultado de la mutación en el panel derecho. Parece que nuestro elemento se creó correctamente, pero solo para comprobarlo podemos volver a ejecutar nuestra primera consulta y ver el resultado:
Puede ver que si agregamos nuestra primera consulta al panel izquierdo del patio de juegos, el botón de reproducción le permite elegir qué operación desea realizar. Finalmente, tenga en cuenta en el paso 3 de la captura de pantalla anterior que nuestro elemento se creó correctamente.
Además de ejecutar la consulta anterior, también podemos buscar en la pestaña "Colecciones" de FaunaDB para ver la colección directamente:
Aprovisionamiento de una nueva clave de base de datos
Ahora que tenemos la base de datos configurada, necesitamos una forma para que nuestra aplicación React acceda a ella.
Para simplificar esta aplicación, esto se hará con una clave secreta que podemos agregar como variable de entorno a nuestra aplicación React. No vamos a tener autenticación para usuarios individuales. En su lugar, generaremos una clave de aplicación que tiene permiso para crear, leer, actualizar y eliminar elementos.
La autenticación y la autorización son temas importantes por sí solos: si desea obtener más información sobre cómo FaunaDB las maneja como ejercicio de seguimiento de esta guía, puede leer todo sobre el tema aquí.
La clave de aplicación que generamos tiene un conjunto asociado de permisos que se agrupan en un "rol". Comencemos definiendo primero un rol que tenga permiso para realizar operaciones CRUD en elementos, así como también realizar la allItems
consulta. Comience yendo a la pestaña "Seguridad" y luego haciendo clic en "Administrar roles":
Hay 2 roles integrados, administrador y servidor. En teoría, podríamos usar estos roles para nuestra clave, pero es una mala idea ya que esas claves permitirían a quien tenga acceso a esta clave realizar operaciones a nivel de base de datos , como crear nuevas colecciones o incluso destruir la base de datos misma. En su lugar, creemos un nuevo rol haciendo clic en el botón "Nuevo rol personalizado":
Puede nombrar la función como desee; aquí usamos el nombre ItemEditor y le otorgamos permiso a la función para leer, escribir, crear y eliminar elementos, así como permiso para leer el allItems
índice.
Guarde este rol y luego diríjase a la pestaña "Seguridad" y cree una nueva clave:
Al crear una clave, asegúrese de seleccionar "ItemEditor" para la función y el nombre que desee:
A continuación se te presentará tu clave secreta que deberás copiar:
Para que React cargue el valor de la clave como una variable de entorno, cree un nuevo archivo llamado .env.local
que se encuentra en el nivel raíz de su aplicación React. En este archivo, agregue una entrada para la clave generada:
REACT_APP_FAUNA_SECRET=fnADzT7kXcACAFHdiKG-lIUWq-hfWIVxqFi4OtTv
Importante: dado que no es una buena práctica almacenar secretos directamente en el control de código fuente en texto sin formato, asegúrese de tener también un .gitignore
archivo en el directorio raíz de su proyecto que contenga .env.local
sus secretos para que no se agreguen a su repositorio de git ni se compartan con otros. .
Es fundamental que el nombre de esta variable comience con " REACT_APP_
", de lo contrario no será reconocida cuando se inicie la aplicación. Al agregar el valor al .env.local
archivo, aún se cargará para la aplicación cuando se ejecute localmente. Tendrá que detener y reiniciar explícitamente su aplicación para yarn start
poder ver estos cambios.
Si está interesado en leer más sobre cómo se cargan las variables de entorno en las aplicaciones creadas mediante create-react-app, encontrará una explicación completa aquí. Cubriremos cómo agregar este secreto como una variable de entorno en Heroku más adelante en este artículo.
Conexión a FaunaDB en React con Apollo
Para que nuestra aplicación React interactúe con nuestra API GraphQL, necesitamos algún tipo de biblioteca cliente GraphQL. Afortunadamente para nosotros, el cliente Apollo proporciona una interfaz elegante para realizar solicitudes de API, así como para almacenar en caché e interactuar con los resultados.
Para instalar los paquetes de Apollo relevantes que necesitaremos, ejecute:
yarn add @apollo/client graphql @apollo/react-hooks
Ahora en el src
directorio de su aplicación, agregue un nuevo archivo llamado client.js
con el siguiente contenido:
import { ApolloClient, InMemoryCache } from "@apollo/client";export const client = new ApolloClient({ uri: "https://graphql.fauna.com/graphql", headers: { authorization: `Bearer ${process.env.REACT_APP_FAUNA_SECRET}`, }, cache: new InMemoryCache(),});
Lo que estamos haciendo aquí es configurar Apollo para realizar solicitudes a nuestra base de datos FaunaDB. Específicamente, el uri realiza la solicitud a Fauna y luego el encabezado de autorización indica que nos estamos conectando a la instancia de base de datos específica para la clave proporcionada que generamos anteriormente.
Hay 2 implicaciones importantes de este fragmento de código:
- El encabezado de autorización contiene la clave con la función "ItemEditor" y actualmente está codificado para usar el mismo encabezado independientemente de qué usuario esté mirando nuestra aplicación. Si actualizara esta aplicación para mostrar una lista de tareas pendientes diferente para cada usuario, necesitaría iniciar sesión para cada usuario y generar un token que podría pasarse en este encabezado. Nuevamente, la documentación de FaunaDB cubre este concepto si desea obtener más información al respecto.
- Como ocurre cada vez que agrega una capa de almacenamiento en caché a un sistema (como lo estamos haciendo aquí con Apollo), presenta la posibilidad de tener datos obsoletos. Las operaciones de FaunaDB son muy consistentes y puede configurar las de Apollo
fetchPolicy
para minimizar el potencial de datos obsoletos. Para evitar lecturas obsoletas en nuestro caché, usaremos una combinación de consultas de recuperación y especificar campos de respuesta en nuestras mutaciones.
A continuación reemplazaremos el contenido del componente de la página de inicio. Dirígete App.js
y reemplaza su contenido con:
import React from "react";import { ApolloProvider } from "@apollo/client";import { client } from "./client";function App() { return ( ApolloProvider client={client} div style={{ padding: "5px" }} h3My Todo Items/h3 divitems to get loaded here/div /div /ApolloProvider );}
Nota: Para esta aplicación de muestra, me estoy centrando en la funcionalidad más que en la presentación, por lo que verá algunos estilos en línea. Si bien ciertamente no recomendaría esto para una aplicación de producción, creo que al menos demuestra cualquier estilo agregado de la manera más sencilla en una pequeña demostración.
Visita http://localhost:3000
nuevamente y verás:
Que contiene los valores codificados que hemos establecido en nuestro jsx anterior. Sin embargo, lo que realmente nos gustaría ver es la tarea pendiente que creamos en nuestra base de datos. En el src
directorio, creemos un componente llamado ItemList
que enumera los elementos de nuestra base de datos:
import React from "react";import gql from "graphql-tag";import { useQuery } from "@apollo/react-hooks";const ITEMS_QUERY = gql` { allItems { data { _id name } } }`;export function ItemList() { const { data, loading } = useQuery(ITEMS_QUERY);if (loading) { return "Loading..."; }return ( ul {data.allItems.data.map((item) = { return li key={item._id}{item.name}/li; })} /ul );}
Luego actualice App.js
para representar este nuevo componente; consulte la confirmación completa en el código fuente de este ejemplo para ver este paso en su totalidad. Al obtener una vista previa de su aplicación nuevamente, verá que su tarea pendiente se ha cargado:
Ahora es un buen momento para confirmar tu progreso en git. Y como usamos Heroku, la implementación es muy sencilla:
git push heroku masterheroku open
Sin embargo, cuando ejecutes heroku open
, verás que la página está en blanco. Si inspeccionamos el tráfico de la red y solicitamos a FaunaDB, veremos un error sobre cómo falta el secreto de la base de datos:
Lo cual tiene sentido ya que aún no hemos configurado este valor en Heroku. Configurémoslo yendo al panel de Heroku, seleccionando su aplicación y luego haciendo clic en la pestaña "Configuración". Allí debe agregar la REACT_APP_FAUNA_SECRET
clave y el valor utilizados en el .env.local
archivo anteriormente. La reutilización de esta clave se realiza con fines de demostración. En una aplicación "real", probablemente tendría una base de datos separada y claves separadas para cada entorno.
Si prefiere utilizar la línea de comando en lugar de la interfaz web de Heroku, puede usar el siguiente comando y reemplazar el secreto con su clave:
heroku config:set REACT_APP_FAUNA_SECRET=fnADzT7kXcACAFHdiKG-lIUWq-hfWIVxqFi4OtTv
Importante : como se indica en los documentos de Heroku, debe activar una implementación para que esta variable de entorno se aplique en su aplicación:
git commit — allow-empty -m 'Add REACT_APP_FAUNA_SECRET env var'git push heroku masterheroku open
Después de ejecutar este último comando, su aplicación alojada en Heroku debería aparecer y cargar los elementos de su base de datos.
Agregar nuevas tareas pendientes
Ahora tenemos todas las piezas en su lugar para acceder a nuestra base de datos FaunaDB tanto localmente como en un entorno Heroku alojado. Ahora agregar elementos es tan simple como llamar a la mutación que usamos anteriormente en GraphQL Playground. Aquí está el código de un AddItem
componente, que utiliza un formulario HTML básico para llamar a la createItem
mutación:
import React from "react";import gql from "graphql-tag";import { useMutation } from "@apollo/react-hooks";const CREATE_ITEM = gql` mutation CreateItem($data: ItemInput!) { createItem(data: $data) { _id } }`;const ITEMS_QUERY = gql` { allItems { data { _id name } } }`;export function AddItem() { const [showForm, setShowForm] = React.useState(false); const [newItemName, setNewItemName] = React.useState("");const [createItem, { loading }] = useMutation(CREATE_ITEM, { refetchQueries: [{ query: ITEMS_QUERY }], onCompleted: () = { setNewItemName(""); setShowForm(false); }, });if (showForm) { return ( form onSubmit={(e) = { e.preventDefault(); createItem({ variables: { data: { name: newItemName } } }); }} label input disabled={loading} type="text" value={newItemName} onChange={(e) = setNewItemName(e.target.value)} style={{ marginRight: "5px" }} / /label input disabled={loading} type="submit" value="Add" / /form ); }return button onClick={() = setShowForm(true)}Add Item/button;}
Después de agregar una referencia a AddItem
nuestro App
componente, podemos verificar que agregar elementos funciona como se esperaba. Nuevamente, puede ver la confirmación completa en la aplicación de demostración para obtener un resumen de este paso.
Eliminar nuevas tareas pendientes
De manera similar a cómo llamamos a la AddItem
mutación generada automáticamente para agregar nuevos elementos, podemos llamar a la DeleteItem
mutación generada para eliminar elementos de nuestra lista. Así es como ItemList
se ve nuestro componente actualizado después de agregar esta mutación:
import React from "react";import gql from "graphql-tag";import { useMutation, useQuery } from "@apollo/react-hooks";const ITEMS_QUERY = gql` { allItems { data { _id name } } }`;const DELETE_ITEM = gql` mutation DeleteItem($id: ID!) { deleteItem(id: $id) { _id } }`;export function ItemList() { const { data, loading } = useQuery(ITEMS_QUERY);const [deleteItem, { loading: deleteLoading }] = useMutation(DELETE_ITEM, { refetchQueries: [{ query: ITEMS_QUERY }], });if (loading) { return divLoading.../div; }return ( ul {data.allItems.data.map((item) = { return ( li key={item._id} {item.name}{" "} button disabled={deleteLoading} onClick={(e) = { e.preventDefault(); deleteItem({ variables: { id: item._id } }); }} Remove /button /li ); })} /ul );}
Al recargar nuestra aplicación y agregar otro elemento, debería aparecer una página similar a esta:
Si hace clic en el botón "Eliminar" de cualquier elemento, DELETE_ITEM
se activa la mutación y se activa la lista completa de elementos al finalizar, según lo especificado en la refetchQuery
opción.
Una cosa que quizás hayas notado es que en nuestro ITEMS_QUERY
, especificamos _id
como uno de los campos que nos gustaría en el conjunto de resultados de nuestra consulta. _id
FaunaDB genera automáticamente este campo como un identificador único para cada colección y debe usarse al actualizar o eliminar un registro.
Marcar elementos como completos
¡Esta no sería una lista de tareas pendientes completamente funcional sin la capacidad de marcar elementos como completos! Así que agreguemos eso ahora. Cuando hayamos terminado, esperamos que la aplicación se vea así:
El primer paso que debemos dar es actualizar nuestro esquema de artículo dentro de FaunaDB ya que en este momento la única información que almacenamos sobre un artículo es su nombre. Dirigiéndonos a nuestro schema.graphql
archivo, podemos agregar un nuevo campo para rastrear el estado de finalización de un elemento:
type Item { name: String isComplete: Boolean}type Query { allItems: [Item!]}
Ahora diríjase a la pestaña GraphQL en la consola de FaunaDB y haga clic en el enlace "Actualizar esquema" para cargar el archivo de esquema recién actualizado.
Nota: también hay una opción "Anular esquema", que puede usarse para reescribir el esquema de su base de datos desde cero si lo desea. Una consideración a tener en cuenta al elegir anular el esquema por completo es que los datos se eliminan de su base de datos. Esto puede estar bien para un entorno de prueba, pero un entorno de prueba o de producción requeriría una migración de base de datos adecuada.
Dado que los cambios que estamos realizando aquí son aditivos, no habrá ningún conflicto con el esquema existente, por lo que podemos conservar nuestros datos existentes.
Puede ver la mutación en sí y su esquema esperado en GraphQL Playground en FaunaDB:
Esto nos dice que podemos llamar a la deleteItem
mutación con un parámetro de datos de tipo ItemInput
. Al igual que con nuestras otras solicitudes, podríamos probar esta mutación en el patio de recreo si quisiéramos. Por ahora, podemos agregarlo directamente a la aplicación. En ItemList.js
, agreguemos esta mutación con este código como se describe en el repositorio de ejemplo.
Las referencias a UPDATE_ITEM
son los cambios más relevantes que hemos realizado. Es interesante observar también que no necesitamos el refetchQueries
parámetro para esta mutación. Cuando regresa la mutación de actualización, Apollo actualiza el elemento correspondiente en el caché según su campo de identificador ( _id
en este caso) para que nuestro componente React se vuelva a representar adecuadamente a medida que se actualiza el caché.
Ahora tenemos todas las funciones para una versión inicial de una aplicación de tareas pendientes. Como paso final, empuja tu rama una vez más hacia Heroku:
git push heroku masterheroku open
Conclusión
¡Tómate un momento para darte una palmadita en la espalda! Ha creado una nueva aplicación React, ha agregado persistencia a nivel de base de datos con FaunaDB y puede realizar implementaciones disponibles para todo el mundo con solo presionar una rama en Heroku.
Ahora que hemos cubierto algunos de los conceptos detrás del aprovisionamiento y la interacción con FaunaDB, configurar cualquier proyecto similar en el futuro es un proceso sorprendentemente rápido. Poder aprovisionar una base de datos accesible a GraphQL en minutos es un sueño para mí cuando se trata de poner en marcha un nuevo proyecto. No solo eso, sino que se trata de una base de datos de nivel de producción que no tiene que preocuparse por configurar o escalar y, en cambio, puede concentrarse en escribir el resto de su aplicación en lugar de desempeñar el papel de administrador de base de datos.
Deja un comentario