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.
Introducción
Integración de GraphQL con Node.js y Express
Integración de GradQL en API REST existente
Organización del Código para Implementar GraphQL
Fundamentos de GraphQL
Creación de tu primer servidor GradQL con Apollo Server Express
Consumo de API GradQL con Insomnia y Postman
Tipado en GraphQL
Sistema de Tipado en GradQL: Queries y Mutations
Tipos de Datos Básicos en GraphQL: Int, Float, String, Boolean, ID
Validación de Nullables y Listas en GradQL
Creación y Manejo de Tipos de Datos en GraphQL
Integración de Archivos GradQL y Live Reloading en Node.js
GraphQL en la Tienda con Express.js
Organización de Resolvers con el Principio de Responsabilidad Única
Conexión de Base de Datos y Consultas en GraphQL con SQLite
Creación de productos con mutations e inputs en GraphQL
Uso de Variables y Alias en GradQL para Optimizar Consultas
Actualización y Eliminación de Productos con GraphQL
Anidamiento de Entidades con GradQL en APIs
Autenticación con GraphQL y JSON Web Tokens
Autenticación de Usuarios con JWT en GraphQL y Node.js
Creación de Mutación para Categorías con Validación de Roles
Autenticación de JSON Web Tokens en GraphQL con Passport
Validación de Roles y Sesiones con GradQL y Express
Validación de Datos en GradQL con Scalars Personalizados
Bonus
Anidamiento dinámico en GraphQL: optimización de consultas SQL
Conclusiones
Comparativa entre WPI y GradQL: Ventajas y Desventajas
Creación de Servidores GradQL con Node.js
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
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.
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.
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í:
CategoryResolvers
.// Dentro de resolvers.js
const resolvers = {
Query: {
category: GetCategory, // Asocia la query 'category' con el resolver GetCategory
},
// Otros resolvers
};
query {
category(id: 1) {
id
name
}
}
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:
products
en el esquema de la categoría que retorne un array de tipo Product
, asegurando que no haya valores nulos.products
sea solicitado, realizando consultas solo en ese momento.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:
products
, evitando así unir datos innecesarios y ahorrar recursos.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
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.
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);
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}})
}
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.
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?