GraphQL y ApolloServer

Construir un API declarativa para potenciar esquemas

Dragon Nomada
React Adventure
8 min readMar 4, 2021

--

Un cohete espacial despegando, referente al despliegue del servidor. Tomado de https://unsplash.com/photos/woWf_VJ7dNs

Cuándo pensamos en arquitecturas para servicios web, REST es una de las más importantes, ya que a través de rutas bien construidas, permiten realizar peticiones al servidor, indicando que entidad se desea consultar, las acciones y demás. Generalmente este sistema se basa en HTTP con peticiones de tipo GET, POST, PUT, DELETE, entre otros métodos de acceso. Sin embargo, cada vez está cobrando más fuerza la metaprogramación, claro ejemplo, son las bases de datos no relacionadas, que funcionan más bien como grafos de datos. También podemos observar grandes avances en la programación funcional y la conceptualización de grafos a nivel programador. Con todo esto, GraphQL se suma a batalla por establecer una mejor forma de hacer las cosas, más colaborativa e ideal para programadores aventurados y expertos.

GraphQL es un lenguaje de consulta basado en esquemas, este ha sido promovido por Facebook. Consiste en declarar y formalizar tipos de entidades, los cuáles parten de tipos de datos básicos, que se estructuran en tipos cada vez más complejos, para lograr establecer esquemas funcionales, que abstraigan la complejidad de los datos. Por ejemplo, podemos definir personas, estudiantes, productos, facturas, usuarios, clientes, compras, ventas, y un sin fin de tipos necesarios para operar nuestro negocio y aplicación. La idea central es definir Tipos, Consultas y Mutaciones, principalmente. Donde los Tipos vienen a ser conjuntos de campos relacionados, las consultas son proyecciones de datos que declaran lo que se desea obtener y las mutaciones son operaciones sobre los esquemas formalizadas.

Definición de Tipos

Lo principal es definir los tipos, estos pueden ser esquemas, consultas o mutaciones, principalmente. La definición de los tipos se hace de lado del servidor.

Sintaxis — Esquema

Donde Persona, Direccion y Ubicacion son esquemas que retienen sus propios campos. Los tipos ID, String, Int, Float y Boolean, son tipos básicos predefinidos.

Las consultas son tipos Query adaptados, que realizan peticiones de datos al grafo resolutor, estos proyectan los valores de cada tipo y entregan lo necesario solamente. Se pueden hacer consultas generales sobre cualquier tipo, incluidos tipos simples.

Sintaxis — Consulta

Sintaxis — Mutación

Construcción de Consulta

Las consultas y mutaciones definidas, pueden ser consumidas mediante el envío de la petición a graphql, por ejemplo, en React, podemos usar el hook useQuery para enviar dicha consulta. Estas se definen de lado del cliente.

Sintaxis — Consulta Simple (sin proyección)

Las consultas que devuelven un tipo definido (una entidad definida), requieren que se proyecten los campos a devolver, por ejemplo, si el query es de la forma primeraPersona: Persona, entonces, el query necesita saber que campos de Persona se devolverán. Así para cada campo que sea de tipo definido, por ejemplo, si se desea devolver el campo dirección, entonces, se debe indicar, que campos de Direccion regresar.

Sintaxis — Consulta de Tipo (con proyección)

Las consultas que han marcado parámetros, deben enviar además del nombre del query, sus parámetros, cada uno con el valor correspondiente, para realizar la consulta.

Sintaxis — Consulta de Tipo con Parámetros

Una consulta puede prepararse para recibir variables, esto permite que al mandar la consulta, los valores de los parámetros, sean extraídos del valor de las variables, que se mandan por separado. Para conseguirlo, debemos nombrar el query, luego, debemos indicar las variables soportadas y sus tipos, finalmente, dentro de la consulta, podemos usar las variables en lugar de poner los valores directos en los parámetros.

Sintaxis — Consulta de Tipo con Variables

Donde peopleList es el nombre del query, $page y $pageSize son las variables recibidas en el query. Las variables son usadas dentro de listaPersona sustituyendo el valor en cada parámetro, por la variable.

Observa que el valor exacto de cada variable se pasa por separado, haciendo que el query sea más genérico.

Las mutaciones son en esencia similares a las consultas. Estas tienen una estructura idéntica en cuánto al paso de parámetros y proyección de los tipos. Sin embargo, las mutaciones son utilizadas para realizar operaciones o transacciones, donde el servidor tomará los valores de los parámetros, para en lugar de hacer consultas, se realicen transacciones de inserción, actualización, eliminación, y demás.

Sintaxis — Mutación Simple

Al igual que las consultas, las mutaciones pueden recibir parámetros, observa que la proyección siempre es sobre la salida, tanto en mutaciones como en consultas.

Sintaxis — Mutación con Parámetros

Al igual que las consultas, las mutaciones soportan variables, la estructura es similar.

Sintaxis — Mutación con Variables

Resolvedores e Implementación

Los “resolvedores” o “resolutores”, son las funciones que implementan las transacciones finales, es decir, implementan el código necesario de lado del servidor, para resolver la consulta o mutación. Estas funciones pueden ser asíncronas y su objetivo es recibir los parámetros de la consulta o mutación y devolver el objeto que cumpla con el tipo especificado en la salida, en el caso de tipo definidos, deberá devolver objetos con las claves y valores adecuados.

Sintaxis — Resolvedores

Configuración del Servidor Apollo en Node.js

Para configurar el servidor, debemos crear un archivo de node/javascript como server.js e instalar los módulos @apollo-server y graphql. Luego, podemos ejecutar nuestro archivo con node server.js, y se montará el servidor en http://localhost:4000. Si nos dirigimos a esa url, podremos ver un playground que nos permitirá realizar consultas y mutaciones de forma interactiva.

Observa que los tipos son definidos mediante gql bajo la sintaxis especial de los string templates. Dentro de las definiciones colocaremos todos los tipos, consultas, mutaciones y demás definiciones de graphql. También hemos definido los resolvedores que implementan las salidas de las consultas. Finalmente instanciamos un servidor con los tipos definidos y los resolutores, escuchando el puerto por defecto, y cuando el servidor está listo, mostramos la url que será http://localhost:4000.

El resultado, será un servidor local de Apollo, el cual podremos usar para interactuar con GraphQL.

La interfaz de Apollo Playground sobre http://localhost:4000

Peticiones Fetch a GraphQL

Bajo la ruta http://localhost:4000/graphql se encontrará nuestro punto de API para hacer manualmente las operaciones fetch de consumo, prueba poner en una terminal curl http://localhost:4000/graphql -H 'content-type: application/json' -d '{ "query": "query { hello}" }'.

El resultado de llamar directamente al api mediante un fetch de tipo curl desde la terminal.

Configuración del Cliente Apollo en React

Aunque podemos hacer peticiones fetch manualmente, lo recomendable es configurar el proveedor de Apollo y usar los hooks useQuery y useMutation, para que a través de React tengamos todo más fácil.

Primero configuraremos un cliente de Apollo, este indicará el link del servidor y preparará el caché en memoria. Luego ese cliente será pasado al proveedor, y, a través de gql, definiremos las consultas y mutaciones de consumo. Finalmente, mediante los hooks useQuery y useMutation, podremos consumir dichos datos. Observa que la consulta implica obtener datos, saber si se están obteniendo y si hubo error.

Nota: Es necesario tener el servidor iniciado en http://localhost:4000 para ver los resultados de este proyecto.

Conclusiones

GraphQL es una nueva arquitectura para realizar peticiones al servidor, esta a diferencia del REST con peticiones imperativas, usa peticiones declarativas. REST tiene múltiples puntos de salida, básicamente, uno por cada ruta del API. Sin embargo, GraphQL provee un único punto de salida, donde todas las peticiones apuntarán a /graphql, de tal forma, que los resultados esperados, sean declarados bajo el lenguaje especificado de GraphQL.

Los siguientes puntos, son un compendio de conceptos e ideas detrás de esta tecnología.

  • Los tipos definen entidades y esquemas, mediante campos que especifican sus propios tipos, por ejemplo, una persona, que se define a partir de los campos id de tipo ID, nombre de tipo String, correo de tipo String, direccion de tipo Direccion, edad de tipo Int, casado de tipo Boolean.
  • Los campos pueden ser opcionales o requeridos. Un campo opcional tendrá la posibilidad de contener un valor nulo o null, mientras que un campo requerido, no podrá contener valores nulos. Para marcar un campo como requerido, debemos poner el símbolo ! después del tipo, por ejemplo, id: ID!, name: String!, school: Institute!.
  • Existe un tipo especial llamado type Query, el cual definirá las posibles resoluciones de consultas permitidas, por ejemplo, type Query { searchStudent(name: String!): Student! }.
  • Las consultas Query pueden llevar o no parámetros y siempre devuelven un tipo de salida. Por ejemplo, sin parámetros allStudents: [Student!], con parámeros searchStudent(name: String!, grade: Grade, school: Institute): [Student!].
  • Existe un tipo especial llamado type Mutation, el cual definirá las posibles resoluciones de transacciones permitidas, por ejemplo, type Mutation { addStudent(name: String!, school: Institute, grade: Grade) }.
  • Las mutaciones y consultas son similares en definición e implementación, sin embargo, las mutaciones deben ser usadas para realizar modificaciones a los datos, por ejemplo, inserciones, actualizaciones, eliminaciones, etc.
  • Las consultas se implementan del lado del cliente y consumen las consultas definidas del lado del servidor, estas deben especificar la proyección del tipo de salida definido. Por ejemplo, para type Query { firstStudent: Student } definida en el servidor, tenemos la consulta de consumo query { firstStudent { name email grade { level } } } del lado del cliente, observa que de lado de la definición en el servidor, se especifica el tipo de salida, pero del lado de consumo en el cliente, se proyecta la salida en los campos que se quieran consumir.
  • Las consultas de consumo pueden enviar parámetros y sus valores, por ejemplo, query { searchStudent(name: "Michael") }, si la consulta definió parámetros obligatorios mediante !, estos deben ponerse forzosamente. Si la consulta no especifica parámetros, como en type Query { firstStudent: Student! }, entonces el consumo se hace sin parámetros, como en query { firstStudent { name, tutor { name email } } }. Observa que siempre se proyecta el tipo en las salidas.
  • Los valores colocados en las consultas de consumo, pueden provenir de variables, para generalizar las consultas y hacerlas reutilizables. Por ejemplo, query mySearchStudentQuery($name: String!, institute: Institute) { searchStudent(name: $name, institute: $institute) }. Observa que las variables $name y $student son usadas como valor en los parámetros. Los valores de las variables, se pasan por separado.

Con esto tenemos un primer panorama amplio de GraphQL, en próximos artículos, profundizaremos con detalles y temas más avanzados, como el uso de fragmentos, enumeraciones, tipos de entrada, suscripciones en tiempo real, y un sin fin de cosas que faltan por descubrir.

--

--

Dragon Nomada
React Adventure

I love maths, algorithms, artificial intelligence, data science and zen's philosophy