Introducción a GraphQL, Queries y Mutations

Posted on Nov 13, 2017 · 1681 words · 8 minute read

Como algunos de ustedes sabrán llevo poco mas de 1 año trabajando con una startup (si, deje Oracle XD) cuyo stack esta conformado en su mayoría por tecnologías de Javascript (NodeJS, ReactJS, Redux, Apollo, GraphQL, React-native, etc). y en esta ocasión quiero compartir con ustedes el material de la platica que di en el GDLJS del mes de octubre en Guadalajara, se trata de una breve introducción a GraphQL y cual ha sido mi experiencia con esta tecnología.

¿Que es GraphQL?

Primero lo primero, GraphQL es un lenguaje de consultas para tu API creado por Facebook en 2012, es decir, es un intermediario comúnmente utilizado entre un cliente y algún orm de tu elección, es importante mencionar que GraphQL no se conecta directamente a tu base de datos, en lugar de eso ayuda a que el cliente defina el formato de la respuesta que desea obtener del servidor, mas adelante veremos algunos ejemplos.

¿Cual es la diferencia?

Ya existen bastantes frameworks para desarrollar apis ¿Por que quisiera usar GraphQL?

Bueno una de las principales diferencias con apis basadas en REST simple que tienen múltiples endpoints es que en tu api basada en GraphQL solo tendras uno.

Ademas de eso las apis comunes utilizan varios métodos HTTP (GET, POST, DELETE, PUT, OPTIONS, etc) según la operación que vayan a realizar, mientras que con GraphQL usaras solamente POST si así lo deseas, un endpoint para gobernarlos a todos 😉

Todo bien hasta aquí, pero no me haz dicho realmente cual es el beneficio de usar esta tecnología

Tranquilo pequeño saltamontes, consideremos el caso siguiente:

Del lado izquierdo tenemos un cliente que hace una petición GET a un endpoint de álbumes pasando un id para obtener sus assets, posteriormente por cada uno de esos assets solicita los comentarios (múltiples peticiones al servidor), adicionalmente los objetos JSON que reciba en las respuestas siempre tendrán los mismos atributos.

Del lado derecho vemos la petición POST equivalente para un endpoint basado en GraphQL, como podemos observar en el mismo payload de nuestra petición estamos indicando el formato de respuesta que queremos que el servidor nos regrese, atributos en los objetos, etc.

Habra quien diga que puede ingeniárselas para que la petición defina la respuesta del servidor, regresar atributos dinámicamente, etc. y le creo pero buena suerte manteniendo algo como esto 🙂

GET /albums/1/assets/comments/?include=asset.name,comment.author,comment.text

Este es precisamente el problema que GraphQL resuelve, GraphQL nos permite definir relaciones entre las entidades de nuestra aplicación e inyectar esos objetos relacionados en las respuestas cada vez que el cliente lo pida.

El siguiente ejemplo de código esta basado en Javascript utilizando expressJS, supongamos que el cliente necesita desplegar en su frontend un objeto como el siguiente:

Un objeto película con datos como su nombre, el año y la calificación de la critica, adicionalmente también queremos los datos de los actores involucrados y los comentarios de los visitantes que han visto esa pelicula.

Manos a la obra, vamos a iniciar un nuevo proyecto con NodeJS

$ npm init  
$ npm install –save express express-graphql graphiql graphql

Adicionalmente me gusta definir algunos comandos e instalar algunas dependencias para tener soporte es6, aquí pueden ver como queda mi package.json al final.

La estructura del proyecto es mas o menos la siguiente (demo-server)

Los archivos y carpetas mas importantes son:

  • app.js es nuestro entry point
  • graphql es la carpeta donde guardaremos nuestros “objetos QL"
  • data es la carpeta donde tendremos algunos objetos de ejemplo que simulan registros de la base de datos

Vamos a comenzar con el objeto Movie (película), en la carpeta graphql creamos un nuevo archivo llamado movieQL.js

import {  
GraphQLObjectType,  
GraphQLInt,  
GraphQLString,  
GraphQLList,  
GraphQLBoolean,  
GraphQLNonNull,  
GraphQLFloat,  
} from 'graphql';

import actorQL from './actorQL';  
import commentQL from './commentQL';

const movieQL = new GraphQLObjectType({  
name: 'movieQL',  
description: 'This is a movie QL object',  
fields: () => {  
return {  
name: {  
type: GraphQLString,  
resolve(movie) {  
return movie.name;  
}  
},  
score: {  
type: GraphQLFloat,  
resolve(movie) {  
return movie.score;  
}  
},  
year: {  
type: GraphQLInt,  
resolve(movie) {  
return movie.year;  
}  
},  
actors: {  
type: new GraphQLList(actorQL),  
resolve(movie) {  
return movie.actors;  
}  
},  
comments: {  
type: new GraphQLList(commentQL),  
resolve(movie) {  
return movie.comments;  
}  
},  
}  
}  
});

export default movieQL;

Como podemos observar al inicio estamos haciendo import de varios módulos que representan tipos de datos escalares en graphQL, adicionalmente hacemos import de otras 2 entidades de nuestra aplicación, actorQL.js y commentQL.js, después en el atributo fields de nuestro objeto movieQL definimos varios campos del mismo junto con su tipo y aqui viene lo mas importante, definimos actors como una lista de tipo actorQL y comments como una lista de tipo commentQL, el código de las otras entidades es bastante similar al de movieQL por lo que no lo pondre en el post, pueden revisarlo en el repositorio: actorQL.js y commentQL.js

Queries y mutations

Otro de los conceptos básicos en graphQL son las queries y las mutations, existe toda una teoría detrás pero en resumen:

  • Queries: nos permiten leer datos del servidor (por lo general extraídos de una db)
  • Mutations: Crear / modificar / borrar datos en el servidor

Dentro de la misma carpeta graphql vamos a crear 2 nuevos archivos, queryQL.js y mutationQL.js

import {  
GraphQLObjectType,  
GraphQLList,  
GraphQLString,  
GraphQLInt,  
GraphQLBoolean  
} from 'graphql';

import movieQL from './movieQL';  
import { movies } from '../data';

const query = new GraphQLObjectType({  
name: 'Query',  
description: 'This is the root Query',  
fields: () => {  
return Object.assign({  
getMovies: {  
type: new GraphQLList(movieQL),  
args: {},  
resolve(root, args, request) {  
// do some db queries  
return movies;  
}  
},  
});  
},  
});

export default query;

Para efectos de que esto es un demo no estamos utilizando ningún orm para conectarnos a alguna base de datos, pero ustedes son libres de elegir e implementar el que mas le guste, de la misma forma que en movieQL.js definimos los fields aquí estamos definiendo nuestros “endpoints", por ejemplo estamos diciendo que getMovies es una query que nos regresara una lista de movieQL y estamos haciendo return del objeto movies (que es un objeto de ejemplo que importamos de la carpeta data).

De la misma forma dentro de mutationQL.js declaramos una operación llamada createMovie que nos retornara un objeto tipo movieQL (después de haberlo creado), la parte importante aquí es que por lo general los mutations reciben argumentos (name, year, score, lista de actores, lista de comentarios) y de nuevo, para efectos de que esto es un demo no estamos haciendo nada con los datos que nos enviá el usuario, simplemente los regresamos en la respuesta.

import {  
GraphQLObjectType,  
GraphQLInt,  
GraphQLString,  
GraphQLNonNull,  
GraphQLList,  
GraphQLInputObjectType,  
GraphQLBoolean,  
GraphQLFloat,  
} from 'graphql';

import movieQL from './movieQL';

const actorInputQL = new GraphQLInputObjectType({  
name: 'actorInputQL',  
fields: {  
name: { type: GraphQLString },  
age: { type: GraphQLInt },  
country: { type: GraphQLString },  
},  
});

const commentInputQL = new GraphQLInputObjectType({  
name: 'commentInputQL',  
fields: {  
user: { type: GraphQLString },  
commentary: { type: GraphQLString },  
timestamp: { type: GraphQLString },  
},  
});

const mutation = new GraphQLObjectType({  
name: 'Mutation',  
description: 'This is the root Mutation',  
fields: () => {  
return Object.assign({  
createMovie: {  
type: movieQL,  
args: {  
name: {  
type: new GraphQLNonNull(GraphQLString),  
},  
year: {  
type: GraphQLInt,  
},  
score: {  
type: GraphQLFloat,  
},  
actors: {  
type: new GraphQLList(actorInputQL),  
},  
comments: {  
type: new GraphQLList(commentInputQL),  
},  
},  
resolve(root, args, request) {  
// do something here  
return args;  
},  
},  
});  
},  
});

export default mutation;

Hasta aquí ya tenemos definidos nuestros queries y mutations de ejemplo, ha llegado el momento de definir un schema de graphQL e integrar todo con express, es bastante sencillo, comenzamos creando un archivo llamado schemaQL.js también dentro de la carpeta graphql

import { GraphQLSchema } from 'graphql';  
import queryQL from './queryQL';  
import mutationQL from './mutationQL';

const schemaQL = new GraphQLSchema({  
query: queryQL,  
mutation: mutationQL,  
});

export default schemaQL;

Como podemos ver, simplemente importamos los modulos de queryQL y mutationQL y finalmente en nuestro entry point (app.js) mandamos llamar a graphQL con el schema recién creado.

import express from 'express';  
import GraphHTTP from 'express-graphql';  
import schemaQL from './graphql/schemaQL';

var app = express();

app.use('/graphiql', GraphHTTP({  
schema: schemaQL,  
pretty: true,  
graphiql: true  
}));

app.use('/graphql', GraphHTTP({  
schema: schemaQL  
}));

app.get('/', function (req, res, next) {  
const reponse = {  
message: 'hello world',  
};  
return res.json(reponse);  
});

module.exports = app;

Notaran que tenemos definidos 2 endpoints, graphql y graphiql. GraphiQL es una herramienta bastante útil que viene con el modulo de graphQL, se trata de una pequeña interfaz web desde donde podemos probar nuestras queries y mutations y la cual nos genera una documentación con base en los objetos QL de nuestro código, por ejemplo para probar nuestra query de getMovies seria algo como lo siguiente:

Observen que del lado izquierdo estoy definiendo los atributos que quiero que contengan los objetos de la respuesta, puedo solicitar mas o menos dependiendo de lo que el cliente pida, ayudando bastante a, por ejemplo, reducir el tamaño de los mensajes si la petición se hace desde un cliente móvil.

De la misma forma podemos probar nuestro mutation por medio de graphiQL

Observen como desde el cliente podemos pasar directamente el objeto con sus atributos, incluso los objetos relacionados como la lista de actores y comentarios, ya es cuestión de procesar todo eso en nuestro backend y crear los registros en la base de datos.

Todo bien hasta el momento, ya sabemos utilizar graphiQL, ahora como usamos nuestra api ya en un proyecto real, muy sencillo, cada vez que hacemos un request en google developer toolbar podemos observar cual es el payload que se enviá al servidor:

Podemos tomar ese mismo payload y con la ayuda de POSTMAN enviarlo como raw body a nuestro endpoint de graphQL en /graphql

Observa como el POST request va dirigido a /graphql y no /graphiql, por ultimo desde el mismo POSTMAN podemos ver cual seria el HTTP request generado haciendo clic en el boton code

Finalmente lo único que queda es implementar ese request en tu lenguaje de programación / framework favorito, a continuación dejo la presentación que utilice durante el evento por si necesitan revisarla asi como el repositorio de github donde esta alojado el código de este demo: graphql demo server