DEV Community

Adrian Garay
Adrian Garay

Posted on

Lo básico en GraphQL

Introducción

GraphQL es un lenguaje de consultas para una API, usa un sistema de tipos que definen tu información y se ejecuta del lado del servidor. No está ligado a una base de datos o algún sistema de almacenamiento de la información y en su lugar es manejado por el código que lo define.

Se basa en la definición de tipos que permita consultar fields específicos de objetos y resolver la información requerida en la consulta.

Fields

# Type
type Car {
    model: String!
    manufacturer: String!
}

# Query
{
    car {
        model
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode
# Data resolved
{
    "data": {
        "car": {
            "model": "97"
            "manufacturer": "Ford"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

En el ejemplo anterior tenemos una query en GraphQL que necesita un objeto car que contiene dos fields: model y manufacturer. Un field en resumen es todo aquello que no es un objeto y es de tipo Scalar, en este caso los fields dentro de car resuelven en valores Scalar de tipo String.

El único objeto en este servicio es car compuesto por fields y definido como un type en la parte inicial de la query. Para el contexto de este servicio car es un type object que contiene dos fields que lo componen. De esta manera podemos escribir una consulta que revisa dentro del objeto car y regresa ambos fields.

Scalar Types

Ya que GraphQL se basa en la definición de tipos nos provee algunos valores de tipo Scalar. Estos valores son tipos que permiten resolver la informacion a un valor concreto por ejemplo:

  • Int: enteros con signo de 32-bit
  • Float: números con signo y punto flotante
  • String: secuencia de caracteres UTF-8
  • Boolean: true o false
  • ID: Este Scalar Type representa un identificador único del objeto, este tipo se resuelve en un String

Nos provee de valores establecidos pero no esta cerrado a su definición, algunos servicios de GraphQL te permiten crear tus propios Scalar Types con la condición que cada uno tiene que tener definida la manera de serializar y deserializar la informacion para comunicarse entre el código que lo define y el que lo consulta.

Arguments

A primera vista GraphQL parece un buen lenguaje para la obtención de datos pero los argumentos proveen una forma de consultar la información de manera concreta.

{
    car(id: "10"): {
        model
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode

Cada objeto puede tener cero o n numero de argumentos. En el ejemplo anterior a través de un argumento id permite filtrar la informacion resuelta a solo aquellos car que tienen el id 10

Aliases

Al realizar consultas puede que necesites una manera de identificar la información que regresa la consulta. Esto es simple con el uso de aliases en la consulta.

{
    fordCars: car(manufacturer: "ford") {
        model
        manufacturer
    }
    nissanCars: car(manufacturer: "nissan") {
        model
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode
{
    "data": {
        "fordCars": [
            {
                "model": "97",
                "manufacturer": "ford"
            }
        ],
        "nissanCars": [
            {
                "model": "2010",
                "manufacturer": "nissan"
            }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

Así los aliases se pueden agregar antes de la consulta y especificar el nombre de la entrada que tendrá. Los alias también pueden ser usados en fields para renombrar la llave de salida de la información.

Fragments

Cuando la consulta se convierte en algo repetitivo necesitamos una manera de simplificarlo, para esto tenemos la ayuda de los fragments que como su nombre indica son fragmentos de tipos. Los fragmentos nos permiten encapsular una parte del tipo de dato que necesitamos y de esta manera podemos realizar la misma petición de datos en distintas consultas.

fragment carInformation on Car {
    model
    manufacturer
}

{
    fordCars: car(manufacturer: "ford") {
        ...carInformation
    }
    nissanCars: car(manufacturer: "nissan") {
        ...carInformation
    }
}
Enter fullscreen mode Exit fullscreen mode

Variables

La mayoría de las veces necesitarás consultar la información de manera dinámica, por ejemplo la siguiente consulta estática:

query CarByFord {
    fordCars: car(manufacturer: "ford") {
        model
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode

Le llamamos estática debido a que la información que los autos que obtendremos serán siempre del fabricante ford, si necesitaramos consultar aquellos que sean del fabricante nissan tendríamos que escribir una consulta nueva. Bueno vamos a realizar algunas modificaciones.

query CarByManufacturer($manufacturer: String!) {
    car(manufacturer: $manufacturer) {
        model
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode

De esta manera al ejecutar la query y enviarle la variable $manufacturer esta pasara como argumento en la consulta y permitirá hacer un filtrado dinámico de la informacion.

Si necesitaramos obtener siembre aquellos del fabricante ford y solo nissan si es requerido podríamos usar una variable default.

query CarByManufacturer($manufacturer: String = "ford") {
    car(manufacturer: $manufacturer) {
        model
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode

En caso de no proporcionar la variable por defecto nos regresaría aquellos del fabricante ford.

Directives

Como pudimos observar las variables nos evitan el escribir mas consultas realizar alguna concatenación en nuestra consulta. De igual forma contamos con las directivas que brinda soporte a la modificación dinámica de la estructura de datos que la query va a resolver.

query CarByManufacturer($manufacturer: String = "ford", $includeManufacturer: Bool = false) {
    car(manufacturer: $manufacturer) {
        model
        manufacturer @include(if: $includeManufacturer)
    }
}
Enter fullscreen mode Exit fullscreen mode

Si ejecutamos la query con las siguiente variables obtendremos un arreglo de car con registro del fabricante nissan pero sin incluir el nombre del fabricante debido al valor por defecto de $includeManufacturer es false y la directiva @include modifica la informacion de salida.

# Variables
{
    "manufacturer": "nissan"
}

# Data resolved
{
    "data": {
        "car": [
            {
                "model": "2010"
            }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

Ahora con la directiva @skip el field manufacturer sera skipeado solo si $includeManufacturer es true

query CarByManufacturer($manufacturer: String = "ford", $excludeManufacturer: Bool = false) {
    car(manufacturer: $manufacturer) {
        model
        manufacturer @skip(if: $excludeManufacturer)
    }
}
Enter fullscreen mode Exit fullscreen mode
# Variables
{
    "manufacturer": "nissan"
}

# Data resolved
{
    "data": {
        "car": [
            {
                "model": "2010",
                "manufacturer": "nissan"
            }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

Mutations

Hasta el momento vimos cómo podemos obtener datos pero GraphQL también soporta la modificación de datos. La mayoría de request en API Rest permite la modificación de datos aun cuando se usa el método GET, por convención no se debe de usar este método en una acción que detone la modificación de datos por ello se usa POST para la creación y PUT/PATCH para la actualización. En GraphQL cualquier operación que realice la modificación de datos por convención debería de ser enviada especificando que se trata de una Mutation.

mutation CreateNewCar($model: String!, $manufacturer: String!) {
    createCar(model: $model, manufacturer: $manufacturer) {
        model,
        manufacturer
    }
}
Enter fullscreen mode Exit fullscreen mode

Una mutation podría regresar los datos del objeto que fue creado por lo que a su vez podemos preguntar por cualquiera de los fields del objeto que acabamos de crear.

Pros y contras

Ventajas:

  • Se basa en la definición de tipos, al tener los tipos bien definidos esto puede reducir el error de comunicación entre el cliente y el servidor.
  • Provee una entrada única a la API, por lo que el cliente tiene mayor flexibilidad para realizar peticiones y modificaciones desde la misma.
  • Al tener tipos definidos se puede realizar una petición de los tipos de datos definidos.
  • Permite una buena escalabilidad de la API.

Desventajas:

  • Además de crear el código sólido de backend los desarrolladores también tienen que crear buenos esquemas de información que sean faciles de mantener.
  • La curva de aprendizaje para aquellos que están acostumbrados a trabajar con API Rest es alta.
  • Gran parte del procesamiento de las consultas se realiza en el servidor.
  • Migrar de una API Rest a GraphQL suele ser dificil y bastante caro.

Conclución

Definitivamente la flexibilidad que proporciona GraphQL para la comunicación entre el backend y frontend permite un desarrollo rápido de aplicaciones cliente-servidor. El backend se encarga enteramente de proveer la definición de tipos y el cómo estos se resuelven y el frontend solo tiene que preocuparse por saber qué información puede requerir y entonces realizar queries desde el mismo cliente. Además, al tener un solo punto de entrada para la realización de queries el cliente puede olvidarse completamente de la definición de urls y usar una unica conexion al servidor para realizar peticiones.

Está claro que esta tecnología nacida en el 2012 aún necesita alcanzar cierta madurez que permita a las empresas tomar en cuenta la migración a esto tipo de API.

Fuentes

A query language for your API

Discussion (0)