Introducción

1

Integración de GraphQL con Node.js y Express

2

Integración de GradQL en API REST existente

3

Organización del Código para Implementar GraphQL

Fundamentos de GraphQL

4

Creación de tu primer servidor GradQL con Apollo Server Express

5

Consumo de API GradQL con Insomnia y Postman

Tipado en GraphQL

6

Sistema de Tipado en GradQL: Queries y Mutations

7

Tipos de Datos Básicos en GraphQL: Int, Float, String, Boolean, ID

8

Validación de Nullables y Listas en GradQL

9

Creación y Manejo de Tipos de Datos en GraphQL

10

Integración de Archivos GradQL y Live Reloading en Node.js

GraphQL en la Tienda con Express.js

11

Organización de Resolvers con el Principio de Responsabilidad Única

12

Conexión de Base de Datos y Consultas en GraphQL con SQLite

13

Creación de productos con mutations e inputs en GraphQL

14

Uso de Variables y Alias en GradQL para Optimizar Consultas

15

Actualización y Eliminación de Productos con GraphQL

16

Anidamiento de Entidades con GradQL en APIs

Autenticación con GraphQL y JSON Web Tokens

17

Autenticación de Usuarios con JWT en GraphQL y Node.js

18

Creación de Mutación para Categorías con Validación de Roles

19

Autenticación de JSON Web Tokens en GraphQL con Passport

20

Validación de Roles y Sesiones con GradQL y Express

21

Validación de Datos en GradQL con Scalars Personalizados

Bonus

22

Anidamiento dinámico en GraphQL: optimización de consultas SQL

Conclusiones

23

Comparativa entre WPI y GradQL: Ventajas y Desventajas

24

Creación de Servidores GradQL con Node.js

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Anidamiento dinámico en GraphQL: optimización de consultas SQL

22/24
Recursos

¿Qué es el anidamiento dinámico en GradQL?

GraphQL es un lenguaje de consulta que permite solicitar los datos de manera precisa, asegurando un menor coste en el rendimiento de los servidores. Una de las funcionalidades más interesantes es el anidamiento dinámico, que permite que una propiedad pueda ser directamente un resolver o tenga un comportamiento dinámico. Vamos a profundizar en cómo funciona esta característica con un ejemplo práctico y entender los beneficios que ofrece al crear queries en GradQL.

¿Cómo se obtiene una categoría desde una API REST?

Al trabajar con una API REST, normalmente se hace una solicitud a un determinado endpoint para obtener datos. Por ejemplo, para traer una categoría específica de productos, se obtiene no solo el nombre y el ID de la categoría, sino también los productos asociados a ella:

GET /api/v1/categories/1

Esto devuelve información como el nombre y el identificador de la categoría junto con las relaciones de los productos que pertenecen a dicha categoría.

Sin embargo, en GradQL, si se hace una request por una única categoría, aún no se especifica que se devuelvan los productos. Esto significa que, aunque el backend está preparado para retornar un array de productos, falta mapear esta relación en GradQL.

¿Cómo se implementa una nueva query en GradQL para obtener una categoría?

Primero, se debe crear una nueva query en el esquema de GradQL. Este query permitirá recibir un identificador y devolverá una categoría en específico. La implementación sería así:

  1. Crear una función que obtenga una categoría mediante su ID utilizando CategoryResolvers.
  2. Exportar este resolver y asociarlo en el archivo de resolvers, como se ejemplifica a continuación:
// Dentro de resolvers.js
const resolvers = {
  Query: {
    category: GetCategory,  // Asocia la query 'category' con el resolver GetCategory
  },
  // Otros resolvers
};
  1. Una vez implementado, se puede ir al playground de GraphQL y ejecutar la consulta para una categoría:
query {
  category(id: 1) {
    id
    name
  }
}

¿Cómo se puede extender la query para incluir productos?

Una vez obtenida la categoría básica, se puede extender para incluir productos como una lista opcional. El objetivo es evitar realizar peticiones costosas si no son necesarias. Para ello:

  • Añadir un campo products en el esquema de la categoría que retorne un array de tipo Product, asegurando que no haya valores nulos.
  • Crear un resolver que sea tratado dinámicamente cuando el campo products sea solicitado, realizando consultas solo en ese momento.

¿Cómo maneja GradQL de manera eficiente las consultas anidadas a productos?

En GradQL, es posible especificar que un campo de un objeto sea un propio resolver:

// Resolver para tratar el campo 'products' como un resolver independiente
const Category = {
  products: (parent) => {
    return productService.getProductsByCategory(parent.dataValues.id);
  },
};

Esto significa:

  • Dinámico y Condicional: La relación se resuelve solo si en la consulta se expresa explícitamente el campo products, evitando así unir datos innecesarios y ahorrar recursos.
  • Optimización de consultas: Al no exigir la unión de datos en la base de datos a menos que sea necesario, se minimizan operaciones costosas.

¿Por qué evitar uniones innecesarias en SQL?

Incluir uniones (left joins) innecesarias en SQL puede ser perjudicial para el rendimiento. Al tener relaciones dinámicas con GradQL, se puede decidir hacer consultas solo si es necesario basándose en la entrada directa del cliente, logrando optimizar el uso de recursos del servidor y mejorar la eficiencia general.

Conocer cómo usar estas ventajas de GradQL ayuda a crear aplicaciones más escalables y con mejor gestión de datos. Este enfoque es especialmente útil cuando se tienen varias relaciones de tablas a manejar, ya que permite controlar cuándo y cómo se realizan las consultas.

Aprender a manejar estas funcionalidades ofrece herramientas sólidas para mejorar la respuesta y eficiencia de tus servicios, y estoy seguro de que con práctica, dominarás estos conceptos con éxito.

Aportes 7

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Excelente característica, incentiva a la reutilización de consultas simples y reduce el números de consultas complejas que surgen usando inner join en las consultas sql.

Anidamiento dinámico

Para entender como lograr un anidamiento dinámico, primero tenemos que lograr que cuando le pidamos a GraphQL una categoría específica, nos mande los productos de esta categoría, veamos como:

Añadimos en el esquema la nueva query:

type Query {
  # ...

  # categories
  category(id: ID!): Category
}

type Category {
  # relación entre productos
  product: [Product]
}

Ahora vamos al category.resolvers.js y añadimos la siguiente lógica:

const getCategory = (_, { id }) => {
  return service.findOne(id)
}

module.exports = { 
	// ...
	getCategory
}

Ahora añadimos la función a los resolvers:

const { addCategory, getCategory } = require('./category.resolvers')

const resolvers = {
  Query: {
    // ...
    category: getCategory
  },
  Mutation: {
    // ...
  },
};

module.exports = resolvers

Con esto, si realizamos una consulta solicitando los productos de una categoría, obtendremos los resultados correctamente.

Sin embargo, si observamos los servicios de categoría, notaremos que la relación siempre se carga, incluso si no la solicitamos en la consulta. ¿Cómo podemos solucionar esto?

GraphQL ofrece una herramienta para resolver este problema. Podemos definir un campo como un resolver, lo que nos permite ejecutar código solo cuando ese campo es requerido.

Teniendo esto en cuenta, veamos cómo añadir esta lógica en nuestro servidor:

Primero vamos a nuestro servicio de productos y vamos a crear la función que nos va a enviar estos datos solo cuando los pidamos

class ProductsService {
  // ...
	async getByCategory(id) {
    return await models.Product.findAll({ where: { categoryId: id }});
  }
}

Ahora vamos a nuestro **product.resolver** y vamos a crear lo siguiente:

const ProductServices = require('../services/product.service')
const service = new ProductServices

// ...

// el parent es el campo que ignorábamos
// Lo utilizamos cuando ejecutamos campos de forma dinámica
const getProdutsByCategory = (parent) => {
  const id = parent.dataValues.id;
  return service.getByCategory(id)
}

module.exports = {
  // ...
  getProdutsByCategory
}

Ahora vamos al resolver principal a hacer lo siguiente:

const { /* ... */ getProductsByCategory } = require('./product.resolvers')

const resolvers = {
  Query: {
    // ...
  },
  Mutation: {
    // ...
  },
  // Hacemos que el campo 'products' de 'Category' se ejecute como un resolver
  Category: {
    // En el campo 'products', llamamos a la función 'getProductsByCategory'
    products: getProductsByCategory
  }
};

Hay que tener en cuenta que esta consulta solo se ejecuta si enviamos el campo products, de lo contrarío, no lo va a hacer. Con esto no estamos haciendo consultas innecesarias en nuestra base de datos.

si en el resultado final les arroja un error
es porque en el video no se alcanza a ver que el id va dentro y no fuera

  return service.getByCategory(id);
🤔, al final se traduce en sub consultas, te ahorras los requests browser- nidejs, pero se castiga horriblemente la capa node - bd, ya Que en lugar de una consulta con un join, al final se itera cada resultado padre y se hace una nueva consulta para rellenar el resolver hijo (hacer otra consulta con su respecto trabajo de red ente servidor - bd) … es práctico a nivel front pero a nivel back…. 😬😬😬, bueno sigamos viendo si hay algo que sea más eficiente a nivel backend…
**ANIDAMIENTO DINÁMICO** *El **anidamiento** **dinámico** en* **GraphQL** *se **refiere** a la **capacidad** de **definir** **relaciones** **complejas** **entre tipos** de **datos** de manera **flexible** y **eficiente**. Esto permite que las **consultas*** **GraphQL** ***especifiquen** la **profundidad** y la **estructura** de los **datos** que **desean** **recuperar**, lo que **facilita** la **recuperación** de **datos** **anidados** de manera **intuitiva** y **sin** **necesidad** de **múltiples** **solicitudes**. Con el **anidamiento** **dinámico**, los **clientes** pueden **acceder** a **datos** **relacionados** de **forma** **eficiente**, lo que **mejora** el **rendimiento** de las **consultas** y **reduce** la **sobrecarga** de **red**. Esto hace que* **GraphQL** *sea **especialmente** **adecuado** para **aplicaciones** que **requieren** **recuperación** de datos **jerárquica** y **compleja**, como **aplicaciones** **web** y **móviles**.*

Anidamiento dinamico

se crea un type que rotorne otro type que este relacionado en la base de datos

type Category {
  id: ID
  name: String,
  image: String,
  createdAt: String
  products:[Product!]! 
 }
 type Query {
  allCategories: [Category]
  categoryById(id:ID): Category

 }
 type Product {
  id:ID!
  name:String!
  price:Float!
  description:String!
  createdAt:String!
  image:URL!
  category:Category
 }

definimos ese type en nuestros resolvers, por lo tanto el type debe llamarse igual al definido

const { login } = require("./auth/auth.resolver")
const { allCategories, categoryById , addCategory} = require("./categories.resolver")
const { product, products ,createProduct,deleteProduct,updateProduct, getProductByCategory} = require("./product.resolver")
const { getPersons , findPerson, createPerson} = require("./resolver.person")

const {RegularExpression} = require('graphql-scalars')

const CategoryNameType= new RegularExpression('CategoryNameType', /^[a-zA-Z0-9]{3,8}$/);

const resolvers= {
  Query:{
    saludo: ()=> 'hola mundillo de internet',
    persons:getPersons,
    findPerson,
    allCategories,
    categoryById,
    product,
    products
  },
  Mutation:{
    createPerson,
    createProduct,
    deleteProduct,
    updateProduct,
    addCategory,
    login
  },
  CategoryNameType,
  Category: {
    products: getProductByCategory
  }
}

module.exports = resolvers

getProductByCategory hace la peticion a la base de datos

const getProductByCategory =async(root,_)=>{
// root es el objeto padre del cual ha sido llamado el resolver es decir: Category
  return await service.getByCategory(root.dataValues.id)
// creamos un metodo en el servicio que retorne los productos relacionados a la categoria
// por medio del id 
// asi solo el resolver se ejecuta si en la consulta son requeridos los productos
}

metodo del servicio

async getByCategory(idCategory){
    return await models.Product.findAll({where:{categoryId:idCategory}})
  }

Dynamic nesting

It’s possible to modify the output of a property inside a schema type. To do so, we can use the parent param, which is the first parameter passed to a resolver.

We’ll modify the products property from the Category type which is already created in schema.graphql. So we must do this in the resolvers:

export const resolvers = {
  Query: {
    category: getCategory, // the parent param will contain what this resolver returns
  },
  Mutation: {
    // ...
  },
  // The same name as in the schema.graphql (Category)
  Category: {
    products: getProductsOfCategory, // <--- modifying its output
  },
};

We call Category and modify the output of the products property.

<br>

The function getProductsOfCategory it’s declared inside product.resolvers.ts file. This is what it does:

const service = new ProductsService();

export const getProductsOfCategory = (parent: any) => {
  const categoryId = parent.dataValues.id;
  return service.findProductsByCategory(categoryId);
};

So basically we use the parent parameter, which returns the data that is already retrieved by the category query itself. In the parent we can find the categoryId that we’ll use to make another query to the db, this time only retrieving the products from that specific category.