No he visto mucho material de GraphQL en Español, así que, aprovechando que tengo un poco de tiempo libre, he preparado esta pequeña introducción a esta genial tecnología.
Descripción oficial de GraphQL:
🗒 GraphQL es un lenguaje de consultas para APIs y un entorno server-side para ejecutar consultas usando un sistema de tipos definido por ti en tus datos. GraphQL no está ligado a ninguna base de datos o motor de almacenamiento, en su lugar, está respaldado por tu código existente y tus datos.
Entendemos por GraphQL como un lenguaje de consultas, el cual usa un sistema de tipos previamente definido por nosotros. ¿Por qué entonces, es tan importante GraphQL?
Veamos el siguiente escenario para poder comprender mejor el funcionamiento de GraphQL. Supongamos que tenemos el siguiente endpoint:
/api/v1/project/:id
El cual nos trae un proyecto de la base de datos por medio de su id
. Cada Project
mantiene una dependencia con Manager
. Nuestro endpoint nos traerá algo así:
{
"projectId": "83ngrgn48t5rdg4tedgasdgasg",
"name": "Bot integration for Messenger",
"manager": {
"managerId": "734gnrfgnfngvffasdgfasdngf",
"firstname": "John",
"lastname": "Doe",
"area": "TI"
}
}
Bien, hasta aquí perfecto, tenemos nuestro proyecto junto con su manager. Pero, digamos que a mi no me interesa los datos del proyecto ni del manager, solo me interesa saber a que área pertenece el manager de dicho proyecto. ¿Cómo lo haces?
La manera tradicional es haciendo un filtrado de datos:
const managerArea = project.manager.area
Ya lo tengo, simple. ¿Cuál es el chiste con GraphQL entonces? 🙄 Tranquilo, no te emociones. Te pregunto algo: ¿Es esto eficiente?
Digamos que Manager
tiene una dependencia Area
y Area
otra dependencia con Organizatinn
y así sucesivamente hasta tener una cantidad considerable de documentos anidados. ¿Crees que sería eficiente obtener todos estos documentos para obtener solo uno o dos datos?
GraphQL viene a solucionar este problema de una manera limpia y eficiente. Lo anterior, usando GraphQL quedaría de la siguiente manera:
query {
getProject(id: "83ngrgn48t5rdg4tedgasdgasg") {
manager {
area {
name
}
}
}
}
Donde obtendríamos como respuesta:
{
data: {
getProject: "TI"
}
}
Ahora nuestra solución es mucho más limpia y sobre todo, liviana, optimizando los recursos de red del usuario, sobre todo en dispositivos móviles. No te preocupes si no entiendes aún cómo funciona, lo veremos progresivamente en este tutorial.
Types
Ya vi cómo se usa, pero, ¿Cómo sabe GraphQL que datos debe seleccionar de la base de datos? 🤔
GraphQL no selecciona nada de la base de datos; lo que hace es hacer un mapeo de entidades a lo que se conoce como types. Un tipo es una representación de algún objeto o suceso dentro de nuestra aplicación. Por ejemplo, si tenemos un modelo User
con la siguiente definición:
@Entity()
class User {
@PrimaryGeneratedColumn()
id: int
@Column()
name: string
@Column()
email: string
}
La definición anterior corresponde a Typescript usando el ORM TypeORM
Podemos mapear nuestro tipo User
de la siguiente manera:
type User {
id: Int
name: String
email: String
}
De manera que GraphQL ahora sabe qué propiedades de la entidad coger 😉. Puedes ver los tipos de datos de GraphQL aquí.
Queries
Vale, hasta aquí entiendo bien. Pero, ¿Cómo puedo obtener una respuesta de GraphQL? No entiendo esa parte 🤔. Calma, vamos a ver ello.
Para que GraphQL responda, necesitamos hacerle una consulta. Para que GraphQL sepa las consultas que tiene disponibles, necesitamos primero definir su estructura. Esto se hace así:
Query {
user(id: Int!): User
}
Es súper sencillo: solo definimos el nombre, los argumentos que recibirá y el tipo de dato que retornará. Si prestamos atención es similar una definición de una función. Así mismo, para ejecutar esa consulta, es similar a la ejecución de una función:
{
user(id: 4575) {
name,
email
}
}
Dado que el tipo de retorno es de tipo User
podemos decidir que tipos de datos queremos obtener. En este caso, solo necesitamos name
y email
.
Mutation
Un Mutation
es muy similar a una Query
, de hecho son prácticamente lo mismo. La diferencia radica en la naturaleza de su uso: Query
está destinado a lectura de datos mientras que Mutation
a escritura. Hay algunas diferencias a nivel técnico, pero nada que impacte a nivel de desarrollo.
Al igual que las Query
, las mutaciones se definen:
type Mutation {
createUser(name: String!, email: String!) : User
}
Y se ejecutan de la misma forma; pudiendo también obtener los campos que necesitemos.
{
createUser(name: "Gustavo", email: "gusgarzaki@gmail.com") {
id # solo queremos el id
}
}
Input
Algunas veces necesitamos enviar un conjunto de valores como un solo parámetro sin necesidad definir cada propiedad como argumento. Para este propósito existen los input
types.
input UserInput {
firstName: String
lastName: String
cardId: Int
email: String
}
type Mutation {
createUser(data: UserInput!): User
}
Y de esta manera podemos pasarle un objeto con esas propiedades:
{
firstName: "Gustavo",
lastName: "Garzaki",
cardId: 35551232,
email: "gusgarzaki@gmail.com"
}
Fragment
Ahora que sabemos qué es un Type
, un Query
y un Mutation
y para qué se usan, vamos a ver una estructura utilitaria. Un Fragment
te permite definir un conjunto de campos y luego incluirlos en las consultas. Por ejemplo:
type User {
id: Int
firstName: String
lastName: String
cardId: Int
address: String
email: String
phone: String
}
fragment PersonalData on User {
firstName
lastName
cardId
address
}
Y podemos reusar ese fragmento en cualquier consulta:
{
findUser(id: 1099) {
...PersonalData
}
}
Variables
Tan simples pero a la vez tan esenciales. ¡No podían faltar! En GraphQL, podemos realizar consultas dinámicas a través de variables. Veamos un ejemplo:
query FindUser($id: Int!) {
user(id: $id) {
...PersonalData # hacemos uso de un fragmento para reforzar 😁
}
}
De esta manera podemos tener un catálogo de queries a las cuales solo les pasamos variables en lugar de tener que escribir las consultas cada vez que vamos a realizarlas.
Directives
Hemos visto como las variables nos ayudan a reutilizar consultas. Incluso esto no puede ser suficiente en algunos casos; quizás queramos incluir o excluir cierta información de forma dinámica de acuerdo a algún indicador. Para esto existen las directivas, que, por medio de una variable, nos permiten incluir o excluir ciertos campos. Veamos un ejemplo.
query FindUser($id: Int!, $onlyPersonalData: Boolean! = false) {
user(id: $id) {
...PersonalData
email @skip(if: $onlyPersonalData)
phone @skip(if: $onlyPersonalData)
}
}
Las directivas nos permiten obtener o evitar campos de manera dinámica. En el ejemplo anterior, definimos dos variables: $id
que representa el id del usuario y $onlyPersonalData
que indica si queremos incluir solo los datos personales, de esta manera, el resultado variará de acuerdo a si pasas true
o false
.
💡 GraphQL nos da dos directivas:
@include(if: Boolean)
, para incluir datos en el resultado solo si el argumento estrue
.@skip(if: Boolean)
, para excluir datos en el resultado solo si el argumento estrue
.
Conclusión
Como puedes ver, GraphQL es radicalmente diferente a las APIs convencionales basadas en REST o SOAP; este tiene su propio lenguaje y te permite obtener datos precisos de forma eficiente gracias a su tremenda flexibilidad.
Recuerda que si bien la arquitectura GraphQL no es un reemplazo de las APIs convencionales, todo depende de tus necesidades. 😉
Top comments (2)
Andaba buscando una lectura que me permitiera comprender un poco más sobre GraphQL. ¡Muchas gracias por el tiempo de escribirla!
Creo recordar que el ! se tipea para indicar que dicho tipo de dato es requerido, ¿es así o me equivoco?
Saludos!
¡Muchas gracias Efren!