Qué es Graphql y cómo utilizarlo con Nodejs Express


imagen de cabecera

Según la definición de su propia página, GraphQL es un lenguaje de consulta para APIs que incluye un entorno en tiempo de ejecución para completar esas consultas con sus datos existentes. GraphQL proporciona una descripción completa y comprensible de los datos en su API, brinda a los clientes el poder de pedir exactamente lo que necesitan y nada más, facilita la evolución de las API con el tiempo y habilita poderosas herramientas de desarrollo.


Ampliando la definición podemos decir que Graphql es un leguanje agnóstico a los leguaje de programación y plataformas donde se emplea, para crear una comunicación cliente servidor basada en consultas mediante un único punto de entrada ( endPoint o url).


La mayor diferencia que tiene una API Graphql con una API Rest es que mediante un punto de entrada puede canalizar toda la comunicación, no solo un CRUD sino todos los CRUD de la API. Esto simplifica la API minimizando el número de endPoint que pone a disposición la API.


También se beneficia de las consultas selectivas de los elementos que de verdad se quieren consultar descartando el resto haciendo la comunicación más ligera. Esto, que en aplicaciones sencillas donde la API no se comparte puede no ser crucial, ya que sólo devolverá los datos necesarios. Pero cuando la API se comparte por múltiples aplicaciones es muy probable que en un API rest tradicional viajen muchos datos que son necesarios para cada una de las aplicaciones pero donde ninguna utiliza la totalidad de lo recibido.


Con Gralphql podemos hacer preguntas selectivas a un mismo endpoint y este nos devuelve sólo los valores pedidos, minimizando el peso de la comunicación y optimizándola.


Además, provee una interfaz gráfica de comunicación para probar y realizar consultas en tiempo de ejecución que nos ayuda sobremanera para probar y crear las consultas.


Es cierto que esto no es nada nuevo y que tampoco necesitamos Gralphql para hacer esto. Podemos crear un API de comunicación por protocolo http con un único punto de entrada y devolver datos en función del modelo de entrada sin necesidad de utilizar un lenguaje de consultas específico pero no estaríamos creando un API Rest, sería un API no estándar y tendríamos que definir todas las reglas de comunicación. Que tendríamos que crear nuestro propio sistema de consultas y una guía para que las aplicaciones sepan cómo implementarlas. En otras palabras, estaríamos creando nuestra alternativa a Graphql por no utilizarlo. ¿Ves por donde voy?



Cómo funciona GraphQL:


Lenguaje de consulta: estructura un leguaje de consultas para la comunicación con la API que facilita el acceso a las aplicaciones. Mientras otras arquitecturas de interfaz solo permiten consultas estrictas que a menudo solo garantizan acceder a un solo recurso, las consultas GraphQL se distinguen por una gran flexibilidad. GraphQL permite tanto consultas de lectura como de escritura.


Sistema de tipos: GraphQL trabaja con un sistema propio de tipos con el que puede describir la estructura de los datos. Cada tipo consta de uno o varios campos que, a su vez, contienen sus propios tipos de datos. Este sistema individual sirve a GraphQL como punto de orientación para validar consultas.


Entorno de tiempo de ejecución: por último, GraphQL también ofrece distintos entornos de tiempo de ejecución para servidores para llevar a cabo consultas GraphQL. Este es el propósito de las bibliotecas disponibles para distintos lenguajes de programación, por ejemplo, Go, Java, JavaScript, PHP, Python o Ruby, que otorgan una gran libertad al usuario a la hora de elegir el lenguaje de su API GraphQL.



Al lío, Cómo usar GraphQL con Nodejs Express.


Si quieres también puedes verlo en vídeo:

Podríamos haber usado el api de GraphQL para javascript ya que GraphQL tiene una solución para cada uno de los lenguajes principales que se utilizan pero ya que estamos lo vemos simulando un servidor con lo que nuestro ejemplo se acerca un poquito a una implementación real.


Primero nos creamos un proyecto Node con el comando


npm init -yes

De esta forma nos saltamos las preguntas sobre los metadatos del proyecto. Una vez creado el package.json lanzamos la siguiente sentencia en la consola para instalar GraphQL y GraphQL-Express:


npm install express express-graphql graphql --save

Una vez instalados abrimos el proyecto en nuestro editor favorito y creamos un archivo llamado index.js. En este archivo vamos a escribir el siguiente código:


var express = require('express');
    var { graphqlHTTP } = require('express-graphql');
    var { buildSchema } = require('graphql');
     
     
    var app = express();
     
    app.use(express.static('public'));
     
    const schema = buildSchema(`
     
    type Query{
        saludo: String,
    }
    `);
    const root = {
        saludo: () => {
            return 'Hola Mundo'
        }
    }
     
    app.use('/graphql', graphqlHTTP({
        schema: schema,
        rootValue: root,
        graphiql: true,
    }));
     
    app.listen(4000);
    console.log('Running a GraphQL API server at http://localhost:4000/graphql');

En las tres primeras líneas importamos los módulos que vamos a necesitar. A continuación instanciamos la aplicación express. Luego especificamos donde se encuentran los archivos estáticos. Hasta aquí muy parecido a cualquier aplicación express.


Ahora empezamos con lo específico. BuildSchema es una función que recibe como parámetro el esquema montando en lenguaje GraphQL. Los esquemas podemos crearlos de distintas formas pero para este tutorial utilizaremos sólo ésta. Como se ve es parecido a una estructura JSON. Básicamente estamos definiendo una consulta mediante la definición de tipos en forma de árbol. En este caso hemos creado un esquema que tiene una consulta llamada saludo que devuelve una cadena de caracteres. Finalmente, tenemos el objeto root que nos sirve para crear una implementación del esquema.


Podríamos probarlo directamente en la interfaces que proporciona esto lo vamos a ver después. Ahora vamos a crear un archivo index.html en la carpeta public para poder acceder a ella desde el servidor y desde aquí realizaremos las peticiones Ajax a la API.


Este sería el código del archivo:


<!DOCTYPE html>
    <html>
     
    <head>
        <meta charset='utf-8'>
        <meta http-equiv='X-UA-Compatible' content='IE=edge'>
        <title>Page Title</title>
        <meta name='viewport' content='width=device-width, initial-scale=1'>
    </head>
     
    <body>
        <div>
            <h2>Consulta saludo</h2>
            <button onclick="obtenerSaludo()">Obtener Saludo</button>
            <textarea style="width: 332px; height: 86px;" id="respuesta1"></textarea>
        </div>
      
        <script>
            const httpClient = (query) => {
                return fetch('/graphql', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json',
                    },
                    body: JSON.stringify(query)
                }).then(r => r.json())
     
            }
            const obtenerSaludo = () => {
                const query = {
                    query: `query{
                            saludo
                        }`
                };
                httpClient(query).then(data => {
                    console.log('data returned:', data);
                    document.body.querySelector('#respuesta1').value = JSON.stringify(data, null, 2)
                });
            };
                </script>
    </body>
     
    </html>

Por un lado tenemos un botón y un textarea para mostrar el resultado. En el script tenemos una función que llama nuestra api y que recibe un parámetro que corresponde a la consulta. Creamos esta función para reutilizarla en las siguientes llamadas del tutorial que vamos a crear. Después tenemos definida la función obtenerSaludo. En esta función tenemos la consulta a la que hemos llamado query. Como se ve, es un objeto que tiene una propiedad llamada query. Esta propiedad comunica que estamos haciendo una petición de tipo consulta y dentro de las consultas estamos llamando a la consulta saludo.


Para problar nuestra nueva página lanzamos en la consola :


node index.js

Esta es la página creada. Cuando pulsemos en el botón veremos este resultado:


captura de pantalla

Ya tenemos una consulta sencilla. Ahora vamos con una consulta que recibe parámetros. Imaginemos que tenemos una base de datos. En este caso la vamos a falsear con un array de esta forma:


const fakeDB = [ new Coche(1, 'deportivo')]

A la vez que creamos el array creamos el primer elementos. Este se crea con la invocación al constructor de la clase Coche que todavía no hemos creado. Pues vamos a crearla:


class Coche {
        constructor(id, tipo) {
            this.id = id;
            this.tipo = tipo;
        }
    }

Con nuestra fakeDB creada continuamos creando la consulta en el esquema y en la raíz( root).


type Query{
        saludo: String,
        consultaCoche (id: Int!): Coche,
    }

Si nos fijamos, estamos creando una consulta donde decimos que tendrá un parámetro de entrada nombrado id y de tipo Int ( numérico ) y de salida un objeto de tipo Coche. Pero en el esquema no tenemos este tipo por lo que seguidamente vamos a crearlo quedando así el esquema:


   type Query{
        saludo: String,
        consultaCoche (id: Int!): Coche,
    }
    type Coche{
        id: Int,
        tipo: String
    }

Por último creamos la consulta en el root:


const root = {
        saludo: () => {
            return 'Hola Mundo'
        },
        consultaCoche: ({ id }) => {
            if (!id) {
                throw new Error('id no informado')
            }
            return fakeDB.find(e => e.id === id);
        },
    }

Simplemente hemos recreado la consulta a la base de datos devolviendo el elemento que coincide con el id.


Ahora vamos al index.html y creamos la interface para esta consulta. Por un lado el html para recoger el parametro , el boton de llamada y el textarea donde mostramos la respuesta:


   <div>
        <h2>Consulta coche</h2>

        <input id="cocheConsulta" />

        <button onclick="consultarCoche()">Consultar coche</button>

        <textarea style="width: 332px; height: 86px;" id="respuesta2"></textarea>
        
    </div>

Por otro la función consulta:


   const consultarCoche = () => {
    const valorId = document.querySelector('#cocheConsulta').value;
    const query = {
        query: `query{
                    consultaCoche(id: ${valorId}) {
                    id,
                    tipo
                    }
                }`}
        httpClient(query).then(data => {
            document.body.querySelector('#respuesta2').value = JSON.stringify(data, null, 2)
        })
    };

En este caso vemos como recogemos el valor de input y lo interpolamos en la query. En la siguiente consulta que vamos a crear revisaremos cómo pasar los parámetros como variables. Antes, podemos probarlo relanzando la aplicación:


captura de pantalla

Ya sólo nos queda ver una consulta de modificación. Las consultas que agregan, modifican o eliminan se llaman mutaciones por lo que hay definirlas en el esquema como de tipo mutation y no de tipo query. A continuación tenemos nuestra consulta mutation en el esquema:


   type Mutation{
        nuevoModelo(modelo: ModeloEntrada): Coche
    }

La definimos dentro del tipo Mutation y la llamamos nuevoModelo. Ésta recibe por parámetros un objeto denominado modelo y de tipo ModeloEntrada que no hemos definido todavía. La consulta también devuelve un objeto de tipo Coche que si tenemos ya definido en el esquema. Por lo tanto tenemos que definir el tipo ModeloEntrada:


   input ModeloEntrada{
    tipo: String
    }

Y como antes tenemos que implementar la consulta en el root:


    nuevoModelo: ({ modelo }) => {
        if (!modelo) {
            throw new Error('id no informado')
        }
        const newId = fakeDB.length + 1;
        fakeDB.push(new Coche(newId, modelo.tipo));
        return new Coche(newId, modelo.tipo);
    }

Una vez tenemos montada la consulta de creación vamos a crear nuestra interface para crear nuevos coches en la base de datos:


Html:

   <h2>Guardar coche</h2>
    <input id="guardarCoche" />
    <button onclick="guardarCoche()">guardar nuevo Coche</button>
    <textarea style="width: 332px; height: 86px;" id="respuesta3"></textarea>

y en el script:


   const guardarCoche = () => {
        const valorTipo = document.querySelector('#guardarCoche').value;
        const query = {
            query: `mutation ($tipo: String){
                nuevoModelo(modelo:{tipo:$tipo}) {
                tipo
                }
                }`,
            variables: {
                tipo: valorTipo
            }
        }
        httpClient(query).then(data => {
            document.body.querySelector('#respuesta3').value = JSON.stringify(data, null, 2)
        })
    }

En esta ocasión el parámetro de entrada de la consulta no se está interpolando en el string si no que se está pasando como propiedad dentro de la propiedad variables del objeto query. Es otra forma de pasar valores.

Ejecutamos para ver que queda todo correcto:

captura de pantalla

Con esto ya hemos visto un CRUD básico utilizando Graphql. Sólo me queda comentar que Graphql tiene herramientas para integrarse bastante bien tanto en back como en front donde en este tutorial no hemos utilizado ninguna pero que en un proyecto real puede que sean interesante para acelerar el desarrollo. Por ejemplo Graphql Apollo se integra con la API react solucionar temas recurrentes a la hora de integrar las dos herramientas. También existen soluciones para Phyton, java, Go, Ruby, PHP, Rust…