Índice de contenidos
- 1. Introducción
- 2. Entorno
- 3. Principales características de GraphQL
- 4. Definir el Schema del API
- 5. Resolver las operaciones del API
- 6. Probar la API
- 7. Código fuente del tutorial
- 8. Conclusiones
1. Introducción
En este tutorial vamos a aprender cómo desarrollar una API con GraphQL, una herramienta desarrollada por Facebook
que pretende sustituir a la archiconocida RESTfull, aunque en mi opinión es un «tipo de» y no un «ó».
Estamos ante un nuevo concepto a la hora de desarrollar APIs, ya que GraphQL no es solo una herramienta sino también un lenguaje
que busca ser lo más natural posible cuando pedimos un recurso.
Esta herramienta lleva tiempo entre nosotros, pero ha sido hace unas semanas cuando GitHub ha empezado a exponer en producción
su API con GraphQL, lo que hará que empiece a ganar popularidad y se extienda entre la comunidad
de desarrolladores.
En este tutorial vamos a realizar un pequeño desarrollo con Node.js, ya que creo que es la forma más rápida y simple
de aprender los conceptos de GraphQL sin entrar en la complejidad de un lenguaje o preocuparnos demasiado por el servidor.
Tampoco veremos cómo consumir la API ya que con las herramientas de desarrollo que nos provee GraphQL es suficiente.
Como he dicho, vamos a utilizar JavaScript, pero GraphQL está disponible para TypeScript, Ruby, Python, Java, Scala,
C# (entre otros) y para clientes como React, React Native, Angular 2, Android e iOS. Para consultar todos los lenguajes y herramientas soportadas, podéis
dirigiros la web de GraphQL.
El objetivo del tutorial será crear una API cuyo dominio de negocio son las pizzas :). Se podrán consultar las pizzas disponibles, ver sus ingredientes y añadir nuevas pizzas.
2. Entorno
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3, 500GB Flash Storage).
- Sistema Operativo: Mac OS Sierra 10.12.5
- Entorno de desarrollo: Visual Studio Code 1.12.2
- Node.js 7.9.0 y npm 4.2.0
- Express 4.15.3
- GraphQL 0.9.6, GraphQL Server for Express 0.7.2 y GraphQL-tools 0.11.0
3. Principales características de GraphQL
Estas son los pilares fundamentales de GraphQL:
- En ocasiones solo se necesita un único campo de un recurso, pero el servidor devuelve todos, sin embargo, con GraphQL
es la aplicación la que tiene el control del recurso, reduciendo el ancho de banda y los tiempos. - Con GraphQL se puede acceder a distintos recursos en una única llamada.
- Se elimina el concepto de endpoints, sustituyéndolo por tipos con los indicar solo lo que se necesita.
- Se intenta eliminar el versionado de las APIs pudiendo deprecar el uso de atributos.
- GraphQL desacopla la dependencia con el acceso a las bases de datos.
4. Definir el Schema del API
Esta será probablemente la parte más difícil de un proyecto con GraphQL ya que el Schema definirá las operaciones y tipos que
expondrá nuestra API. Como vemos a continuación, cambia la filosofía de RESTful y nos olvidamos de endpoints y verbos.
Primero se definen las posibles operaciones de nuestra API de pizzas. En este proyecto se usa el plugin «babel-plugin-inline-import»
para leer ficheros *.graphql y modularizar las definiciones. En caso de no usar este plugin habría que definir el Schema en template strings:
schema { query: Query mutation: Mutation } type Query { pizzas (name: [String]): [Pizza] ingredients: [Ingredient] } type Mutation { createPizzas (pizzas: [PizzaInput]): [Pizza] }
Pasemos a analizar el Schema:
- schema: sección donde se definen los tipos de operaciones de nuestra API.
Las Queries serán las consultas que se podrán realizar y las Mutations la forma en la que realizamos modificaciones
en el servidor. - Query: se definen dos consultas: pizzas e ingredients:
- La consulta pizzas puede recibir como parámetros un array de string (name: [String]) y devolverá
un array con objetos de tipo Pizza ([Pizza]). - La consulta ingredients no recibe parámetros y devolverá un array con objetos de tipo Ingredient ([Ingredient]).
- La consulta pizzas puede recibir como parámetros un array de string (name: [String]) y devolverá
- Mutation: se define la modificación createPizzas que creará pizzas y que puede recibir un array de objetos de tipo PizzaInput (pizzas: [PizzaInput]) y que devolverá un array
de objetos de tipo Pizza ([Pizza]) con las pizzas creadas.
Una vez definidas las operaciones, definiremos los tipos que pueden devolver y recibir:
type Pizza { id: Int! name: String! origin: String ingredients: [Ingredient] } type Ingredient { id: Int! name: String! calories: String } input PizzaInput { name: String! origin: String ingredientIds: [Int] }
- Se definen dos tipos Pizza e Ingredient con la palabra reservaba «type», los cuales vimos que usarán las operaciones anteriores pizzas, ingredients y createPizzas. Viendo el tipo Pizza más a fondo se define:
- id: se define el atributo id de tipo integer que no puede ser null (nótese el carácter !).
- origin: se define el atributo origin de tipo string que puede ser null.
- ingredients: se define el atributo ingredients que será un array de objetos
de tipo Ingredient ([Ingredient]).
- Se define un tipo de entrada PizzaInput con la palabra reservada «input»
que será usado por las operaciones de tipo Mutation para recibir parámetros.
Con esto hemos terminado de definir el Schema de nuestra API. Si queréis ver más tipos disponibles podéis dirigiros a la sección Schema de GraphQL.
5. Resolviendo las operaciones del API
Ahora necesitamos decirle a GraphQL cómo resolver las operaciones cuando se reciban peticiones de recurros. Recurriremos a funciones comúnmente llamadas resolvers:
const pizzaResolver = { Query: { pizzas(root, { name }) { // Business logic to get Pizza Object return requestedPizzas; } }, Pizza: { ingredients(pizza) { // Business logic to resolve the field "ingredients" when "Pizza" object is requested return requestedIngredients; } }, Mutation: { createPizzas(root, { pizzas }) { // Business logic to add new pizza to database // The "ingredients" field is autoresolved by GraphQL return newPizzas; } } };
Pasemos a analizar el resolver
- Para resolver la Query pizzas de nuestro Schema, tenemos la función pizzas(root, { name }),
la cual será llamada cuando se realice dicha Query. - Como hemos visto, la Query pizzas puede recibir un parámetro name: [String] y será en el segundo parámetro
de la función pizzas(root, { name }) donde lo reciba. - Uno de los atributos del tipo Pizza es ingredients
y para que GraphQL pueda devolver el array de objetos Ingredient habrá que indicarle cómo resolverlo.
Por lo tanto, cuando se pida el atributo ingredients, el resolver Pizza.ingredients(pizza)
será invocado, recibiendo cada objeto Pizza encontrado y devolviendo un array de Ingredient. - Para resolver la Mutation createPizzas, se invocará a la función createPizzas(root, { pizzas }),
la cual recibirá un objeto input PizzaInput y devolverá cada Pizza creada.
Con esto tendríamos definida y resuelta nuestra API y podríamos empezar a usarlo. En el punto 7 os
dejo el código completo del proyecto para ver la implementación con más detalle.
Podríamos haber definido nuestro Schema y resolver sin usar el lenguaje GraphQL de la siguiente manera:
import graphql from 'graphql'; let pizzaType = new graphql.GraphQLObjectType({ name: 'Pizza', fields: { id: { type: graphql.GraphQLInt }, name: { type: graphql.GraphQLString }, } }); let queryType = new graphql.GraphQLObjectType({ name: 'Query', fields: { user: { type: pizzaType, args: { name: { type: graphql.GraphQLList(String) } }, resolve: function (root, { name }) { return pizzas; } } } }); var schema = new graphql.GraphQLSchema({ query: queryType });
6. Probar la API
Ahora que ya tenemos construida nuestra API vamos a ver los resultados obtenidos con unas cuantas líneas de código. No veremos como consumir la API desde clientes ya que GraphQL nos da una herramienta para probar peticiones, GraphiQL.
Esta es la interfaz de GraphiQL y en ella tenemos los siguientes componentes:
- Una primera sección donde escribir Queries y Mutations. Esta sección se irá autocompletando con lo que vayamos escribiendo e irá haciendo sugerencias de las operaciones y tipos disponibles. Además indicará errores de sintaxis.
- Si lo deseamos, podemos escribir Queries y Mutations como funciones a las que pasar variables desde la sección «QUERY VARIABLES». En este tutorial tenemos consultas simples y no usaremos dicha sección pero nos ayudará a realizar consultas más legibles cuando tengan más complejidad.
- Tenemos una tercera sección en la que poder visualizar los resultados devueltos por el servidor en formato JSON.
- Por último, tendremos la documentación de nuestra API totalmente autogenerada donde podremos ver todas las posibles operaciones y tipos expuestos. Esto es muy importante ya que nos ahorrará horas de documentación.
- Añado las herramientas de desarrollo donde podemos observar la URL de la petición (siempre será la misma) y el Payload con las claves «operationName», «query» y «variables».
Vamos a ver algunas de las consultas que podemos realizar:
Como se puede observar, se pueden pedir todas o solo determinadas pizzas y en la respuesta podemos indicar solo los campos que necesitemos. También se pueden consultar los ingredientes de las pizzas o incluso acceder a varios recursos a la vez (pizzas e ingredients).
A la hora de añadir pizzas, podremos hacer lo mismo con los resultados obtenidos, podemos filtrarlos e incluso ver como se resuelve el campo ingredients. Además, si uno de los parámetros obligatorios no es enviado, GraphiQL lo indicará como un error y el servidor nos devolverá que la operación ha fallado sin que tengamos que reflejarlo en el código (tan solo indicar en el input del Mutation que el campo no puede ser null).
Os dejo alguna operación más en el README del código fuente del tutorial.
7. Código fuente del tutorial
En mi repositorio de GitHub podéis encontrar todo el código del proyecto del tutorial. Para ejecutarlo solo hay que tener instalado Node.js 7.9.0 y ejecutar
los siguientes comandos en un terminal desde el directorio del proyecto: “npm install” y “npm start”.
Tras ejecutar los comandos se podrán hacer peticiones a la API en la URL «http://localhost:3000/pizza_api» o acceder a GraphiQL a través de la URl http://localhost:3000/graphiql.
8. Conclusiones
Como hemos podido ver, GraphQL nos da herramientas para desarrollar un API de forma rápida, natural e independiente del acceso a la base de datos. Además soporta una gran cantidad de lenguajes y clientes.
Durante el desarrollo he podido darme cuenta de que los tipos de schema son limitados ya que el hecho de solo disponer de Query y Mutation limita el tipo de API que podemos construir conceptualmente hablando. Por ejemplo, si quisiéramos implementar un servicio que enviara un email, en mi opinión debería existir un tercer tipo denominado «Service», ya que no es ni una Query ni un Mutation.
Por otro lado, el hecho de que GraphQL no devuelva todos los campos no implica que la consulta si se los traiga de la base de datos.
Un saludo.
Alejandro
Muy interesante artículo introductorio a Graphql. Muchas gracias por tomarte el tiempo para redactarlo. A seguir averiguando sobre esta interesante herramienta, aún no me queda claro como es que encuentra esa información ¿De dónde obtiene la información? bueno, gracias una vez más.
Excelente articulo, me ayudo bastante
Que buena una pregunta si deseas crear un usuario pero al mismo tiempo ya se cree un perfil…..que este relacionada con el id del usuario…..gracias una mutacion dentro de otra de otra se puede?
muy interesante, pero me surge una duda, todo se va a implementar en un solo schema ?
por que cuando sea un proyecto gigante y se necesite crear diferentes schemas para darle un control, un orden que ?
a lo que me refiero es que se debería de tener en cuenta entonces
Hola,
Al principio solo se podía con un solo schema, luego se desarrollaron herramientas para poder unir schemas separados en un único schema. Creo que a día de hoy ya se soportan multi schemas de forma nativa.