GraphQL es la nueva palabra de moda en el desarrollo de las API. Aunque las API RESTful siguen siendo la forma más popular de exponer los datos de las aplicaciones, tienen muchas limitaciones que GraphQL pretende resolver.

GraphQL es un lenguaje de consulta creado por Facebook, que se convirtió en un proyecto de código abierto en 2015. Ofrece una sintaxis intuitiva y flexible para describir y acceder a los datos de una API.

Esta guía explorará cómo construir un proyecto Node.js con GraphQL. Usaremos GraphQL para construir una aplicación Todo en el framework web Express.js para Node.

¿Qué Es GraphQL?

Según la documentación oficial: «GraphQL es un lenguaje de consulta para API y un tiempo de ejecución para realizar esas consultas con los datos existentes. GraphQL proporciona una descripción completa y comprensible de los datos de tu API, da 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 potentes herramientas para desarrolladores.»

GraphQL es un tiempo de ejecución del lado del servidor para ejecutar consultas utilizando el sistema de tipos que hayas definido para tus datos. Además, GraphQL no está vinculado a ninguna base de datos o motor de almacenamiento específico. En su lugar, se apoya en tu código y almacén de datos existentes. Puedes obtener una comparación detallada de estas tecnologías con la guía GraphQL vs. API RESTful.

Para crear un servicio GraphQL, empieza definiendo tipos de esquema y creando campos que utilicen esos tipos. A continuación, proporcionas una función de resolución que se ejecutará en cada campo y tipo cada vez que el cliente solicite datos.

Terminología GraphQL

El sistema de tipos GraphQL se utiliza para describir qué datos se pueden consultar y qué datos se pueden manipular. Es el core de GraphQL. Vamos a discutir las diferentes formas en que podemos describir y manipular datos en GraphQL.

Tipos de Objeto

Los tipos de objetos GraphQL son modelos de datos que contienen campos fuertemente tipificados. Debe haber una correspondencia 1 a 1 entre tus modelos y los tipos GraphQL. A continuación se muestra un ejemplo de Tipo GraphQL:

type User {
  id: ID! # The "!" means required
  firstname: String
  lastname: String
  email: String
  username: String
  todos: [Todo] # Todo is another GraphQL type
}

Consultas

GraphQL Query define todas las consultas que un cliente puede ejecutar en la API GraphQL. Debes definir un RootQuery que contendrá todas las consultas existentes por convención.

A continuación definimos y asignamos las consultas a la correspondiente API RESTful:

type RootQuery {
  user(id: ID): User           # Corresponds to GET /api/users/:id
  users: [User]                # Corresponds to GET /api/users
  todo(id: ID!): Todo    # Corresponds to GET /api/todos/:id
  todos: [Todo]          # Corresponds to GET /api/todos
}

Mutaciones

Si las Consultas GraphQL son peticiones GET, las mutaciones son peticiones POST, PUT, PATCH, y DELETE que manipulan la API GraphQL.

Pondremos todas las mutaciones en un único RootMutation para demostrarlo:

type RootMutation {
  createUser(input: UserInput!): User             # Corresponds to POST /api/users
  updateUser(id: ID!, input: UserInput!): User    # Corresponds to PATCH /api/users
  removeUser(id: ID!): User                       # Corresponds to DELETE /api/users
  createTodo(input: TodoInput!): Todo
  updateTodo(id: ID!, input: TodoInput!): Todo
  removeTodo(id: ID!): Todo
}

Ya has comprobado el uso de tipos -input para las mutaciones como UserInput, TodoInput. Siempre es la mejor práctica para definir los tipos de entrada para la creación y actualización de tus recursos.

Puedes definir los tipos de entrada como se indica a continuación:

input UserInput {
  firstname: String!
  lastname: String
  email: String!
  username: String!
}

Resolvers

Los Resolvers le dicen a GraphQL qué hacer cuando se solicita cada consulta o mutación. Es una función básica que hace el trabajo duro de acceder a la capa de base de datos para realizar las operaciones CRUD (crear, leer, actualizar, eliminar), acceder a un punto final RESTful API interno o llamar a un microservicio para satisfacer la solicitud del cliente.

Puedes crear un nuevo archivo resolvers.js y añadir el siguiente código:

import sequelize from '../models';
export default function resolvers () {
  const models = sequelize.models;
  return {
    // Resolvers for Queries
    RootQuery: {
      user (root, { id }, context) {
        return models.User.findById(id, context);
      },
      users (root, args, context) {
        return models.User.findAll({}, context);
      }
    },
    User: {
      todos (user) {
        return user.getTodos();
      }
    },
  }
  // Resolvers for Mutations
  RootMutation: {
    createUser (root, { input }, context) {
      return models.User.create(input, context);    
    },
    updateUser (root, { id, input }, context) {
      return models.User.update(input, { ...context, where: { id } });
    },
    removeUser (root, { id }, context) {
      return models.User.destroy(input, { ...context, where: { id } });
    },
    // ... Resolvers for Todos go here
  }
}

Esquema

El esquema GraphQL es lo que GraphQL expone al mundo. Por lo tanto, los tipos, consultas y mutaciones se incluirán dentro del esquema para ser expuestos al mundo.

A continuación se explica cómo exponer al mundo los tipos, consultas y mutaciones:

schema {
  query: RootQuery
  mutation: RootMutation
}

En el script anterior, incluimos el RootQuery y el RootMutation que creamos anteriormente para exponerlos al mundo.

¿Cómo Funciona GraphQL con Node.js y Express.js?

GraphQL proporciona una implementación para los principales lenguajes de programación, y Node.js no está exento. En el sitio web oficial de GraphQL, hay una sección para la compatibilidad con JavaScript, y además, hay otras implementaciones de GraphQL para simplificar la escritura y la programación.

GraphQL Apollo proporciona una implementación para Node.js y Express.js y facilita los primeros pasos con GraphQL.

En la siguiente sección aprenderás a crear y desarrollar tu primera aplicación GraphQL en Node.js y el framework backend Express.js utilizando GraphQL Apollo.

Configurar GraphQL con Express.js

Construir un servidor API GraphQL con Express.js es sencillo para comenzar. En esta sección, exploraremos cómo construir un servidor GraphQL.

Inicializar el Proyecto con Express

En primer lugar, necesitas instalar y configurar un nuevo proyecto Express.js. Crea una carpeta para tu proyecto e instala Express.js utilizando este comando:

cd <project-name> && npm init -y
npm install express

El comando anterior crea un nuevo archivo package.json e instala la biblioteca Express.js en tu proyecto.

A continuación, estructuraremos nuestro proyecto como se muestra en la siguiente imagen. Contendrá diferentes módulos para las características del proyecto, como usuarios, todos, etc.

Una lista de archivos en graphql-todo.
Archivos para graphql-todo.

Inicializar GraphQL

Comencemos instalando las dependencias de GraphQL Express.js. Ejecuta el siguiente comando para instalar:

npm install apollo-server-express graphql @graphql-tools/schema --save

Crear Esquemas y Tipos

A continuación, vamos a crear un archivo index.js dentro de la carpeta modules y a añadir el siguiente fragmento de código:

const { gql } = require('apollo-server-express');
const users = require('./users');
const todos = require('./todos');
const { GraphQLScalarType } = require('graphql');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const typeDefs = gql`
  scalar Time
  type Query {
    getVersion: String!
  }
  type Mutation {
    version: String!
  }
`;
const timeScalar = new GraphQLScalarType({
  name: 'Time',
  description: 'Time custom scalar type',
 serialize: (value) => value,
});
const resolvers = {
  Time: timeScalar,
  Query: {
    getVersion: () => `v1`,
  },
};
const schema = makeExecutableSchema({
  typeDefs: [typeDefs, users.typeDefs, todos.typeDefs],
  resolvers: [resolvers, users.resolvers, todos.resolvers],
});
module.exports = schema;

Recorrido del Código

Vamos a trabajar con el fragmento de código y a desglosarlo:

Paso 1

En primer lugar, importamos las bibliotecas necesarias y creamos los tipos de consulta y mutación predeterminados. Por ahora, la consulta y la mutación sólo establecen la versión de la API GraphQL. Sin embargo, ampliaremos la consulta y la mutación para incluir otros esquemas a medida que avancemos.

Una interfaz de línea de comandos que muestra el código "const" para importar GraphQL y otras extensiones.
Importación de GraphQL y extensiones.
Paso 2:

A continuación creamos un nuevo tipo escalar para el tiempo y nuestro primer resolver para la consulta y mutación creadas anteriormente. Además, también generamos un esquema utilizando la función makeExecutableSchema.

El esquema generado incluye todos los demás esquemas que importamos y también incluirá más cuando los creemos e importemos.

Una interfaz de línea de comandos que muestra el código "const" para crear nuestro tipo escalar y nuestro primer resolver.
Creamos un tipo escalar para el tiempo, así como nuestro primer resolver.

El fragmento de código anterior muestra que hemos importado diferentes esquemas en la función makeExecutableSchema. Este enfoque nos ayuda a estructurar la aplicación para que sea más compleja. A continuación, vamos a crear los esquemas Todo y Usuario que hemos importado.

Creación del Esquema Todo

El esquema Todo muestra operaciones CRUD sencillas que pueden realizar los usuarios de la aplicación. A continuación se muestra el esquema que implementa la operación CRUD Todo.

const { gql } = require('apollo-server-express');
const createTodo = require('./mutations/create-todo');
const updateTodo = require('./mutations/update-todo');
const removeTodo = require('./mutations/delete-todo');
const todo = require('./queries/todo');
const todos = require('./queries/todos');
const typeDefs = gql`
  type Todo {
    id: ID!
    title: String
    description: String
    user: User
  }
  input CreateTodoInput {
    title: String!
    description: String
    isCompleted: Boolean
  }
  input UpdateTodoInput {
    title: String
    description: String
    isCompleted: Boolean
  }  extend type Query {
    todo(id: ID): Todo!
    todos: [Todo!]
  }
  extend type Mutation {
    createTodo(input: CreateTodoInput!): Todo
    updateTodo(id: ID!, input: UpdateTodoInput!): Todo
    removeTodo(id: ID!): Todo
  }
`;
// Provide resolver functions for your schema fields
const resolvers = {
  // Resolvers for Queries
  Query: {
    todo,
    todos,
  },
  // Resolvers for Mutations
  Mutation: {
    createTodo,
    updateTodo,
    removeTodo,
  },
};
module.exports = { typeDefs, resolvers };

Recorrido del Código

Analicemos el fragmento de código y desglosémoslo:

Paso 1:

En primer lugar, creamos un esquema para nuestro Todo utilizando GraphQL type, input, y extend. La palabra clave extend se utiliza para heredar y añadir nuevas consultas y mutaciones a la consulta root y mutación existentes que creamos anteriormente.

Una interfaz de línea de comandos que muestra el esquema de nuestro script Todo, incluyendo nuevas entradas.
Creando el esquema para nuestro Todo.
Paso 2:

A continuación, creamos un resolver, que se utiliza para recuperar los datos correctos cuando se llama a una consulta o mutación determinada.

Una interfaz de línea de comandos que muestra el código para crear un resolver para nuestro Todo.
Creación de un resolver.

Una vez creada la función resolver, podemos crear métodos individuales para la lógica de negocio y la manipulación de la base de datos, como se muestra en el ejemplo create-todo.js.

Crea un archivo create-user.js en la carpeta ./mutations y añade la lógica de negocio para crear un nuevo Todo en tu base de datos.

const models = require('../../../models');
module.exports = async (root, { input }, context) => {
  return models.todos.push({ ...input });
};

El fragmento de código anterior es una forma simplificada de crear un nuevo Todo en nuestra base de datos utilizando el ORM Sequelize. Puedes obtener más información sobre Sequelize y cómo configurarlo con Node.js.

Puedes seguir el mismo paso para crear muchos esquemas dependiendo de tu aplicación o puedes clonar el proyecto completo desde GitHub.

A continuación, vamos a configurar el servidor con Express.js y ejecutar la aplicación Todo recién creada con GraphQL y Node.js.

Configurar y Ejecutar el Servidor

Por último, vamos a configurar nuestro servidor utilizando la biblioteca apollo-server-express que instalamos anteriormente y configurarlo.

apollo-server-express es un simple wrapper del Servidor Apollo para Express.js, se recomienda porque ha sido desarrollado para adaptarse en el desarrollo de Express.js.

Utilizando los ejemplos que hemos comentado anteriormente, vamos a configurar el servidor Express.js para que funcione con la recién instalada apollo-server-express.

Crea un archivo server.js en el directorio root y pega el siguiente código:


const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const schema = require('./modules');
const app = express();
async function startServer() {
  const server = new ApolloServer({ schema });
  await server.start();
  server.applyMiddleware({ app });
}
startServer();
app.listen({ port: 3000 }, () =>
  console.log(`Server ready at http://localhost:3000`)
);

Con el código anterior, has creado con éxito tu primer servidor CRUD GraphQL para Todos y Usuarios. Puedes iniciar tu servidor de desarrollo y acceder al playground utilizando http://localhost:3000/graphql. Si todo va bien, debería aparecerte la siguiente pantalla:

Una interfaz de desarrollo que muestra una consulta simple en respuesta.
La pantalla de verificación.

Resumen

GraphQL es una tecnología moderna respaldada por Facebook que simplifica el tedioso trabajo que supone crear API a gran escala con patrones arquitectónicos RESTful.

Esta guía ha esclarecido GraphQL y demostrado cómo desarrollar tu primera API GraphQL con Express.js.

Haznos saber qué construyes utilizando GraphQL en los comentarios a continuación.

Solomon Eseme

Soy un Ingeniero de Software y Creador de Contenidos orientado a crear productos innovadores y de alto rendimiento siguiendo las mejores prácticas y los estándares del sector. También me encanta escribir sobre ello en Masteringbackend.com. Sígueme en X, LinkedIn y About Me