Un tutorial completo de las API GraphQL con React y FaunaDB

Índice
  1. Nuestra aplicación
  2. Creando la aplicación React
  3. Implementación en Heroku
  4. Aprovisionamiento de una base de datos FaunaDB
  5. Creando una API a través del esquema GraphQL en FaunaDB
  6. Aprovisionamiento de una nueva clave de base de datos
  7. Conexión a FaunaDB en React con Apollo
  8. Agregar nuevas tareas pendientes
  9. Eliminar nuevas tareas pendientes
  10. Marcar elementos como completos
  11. 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:3000y 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.graphqlarchivo 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 Itemtipo con un namecampo de cadena y que tendremos una consulta explícita allItemspara 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.graphqlarchivo 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 allItemsconsulta, FaunaDB ha generado las siguientes consultas/mutaciones automáticamente en nuestro nombre:

  • findItemByID
  • createItem
  • updateItem
  • deleteItem

Todos estos se derivaron declarando el Itemtipo 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, createItemuna 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 allItemsconsulta. 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.localque 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 .gitignorearchivo en el directorio raíz de su proyecto que contenga .env.localsus 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.localarchivo, aún se cargará para la aplicación cuando se ejecute localmente. Tendrá que detener y reiniciar explícitamente su aplicación para yarn startpoder 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 srcdirectorio de su aplicación, agregue un nuevo archivo llamado client.jscon 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:

  1. 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.
  2. 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 fetchPolicypara 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.jsy 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:3000nuevamente 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 srcdirectorio, creemos un componente llamado ItemListque 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.jspara 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_SECRETclave y el valor utilizados en el .env.localarchivo 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 AddItemcomponente, que utiliza un formulario HTML básico para llamar a la createItemmutació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 AddItemnuestro Appcomponente, 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 AddItemmutación generada automáticamente para agregar nuevos elementos, podemos llamar a la DeleteItemmutación generada para eliminar elementos de nuestra lista. Así es como ItemListse 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_ITEMse activa la mutación y se activa la lista completa de elementos al finalizar, según lo especificado en la refetchQueryopción.

Una cosa que quizás hayas notado es que en nuestro ITEMS_QUERY, especificamos _idcomo uno de los campos que nos gustaría en el conjunto de resultados de nuestra consulta. _idFaunaDB 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.graphqlarchivo, 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 deleteItemmutació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_ITEMson los cambios más relevantes que hemos realizado. Es interesante observar también que no necesitamos el refetchQueriespará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 ( _iden 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.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion