GraphQL nos brinda la habilidad de establecer relaciones entre tipos de manera sencilla, lo que nos permite modelar datos más complejos. En esta guía, aprenderás cómo agregar un campo de tipo arreglo que representa una relación con otros tipos y cómo crear un mutation para actualizar y manejar estas relaciones.
¿Cómo definir y actualizar un campo tipo arreglo?
Un aspecto esencial al trabajar en GraphQL es aprender a crear campos de tipo arreglo que contienen elementos de otro tipo. Aquí te mostramos cómo hacerlo.
Definición del campo en el esquema:
Vamos a definir en nuestro esquema que un Curso tenga un campo llamado pipo, el cual será un arreglo de tipo Estudiante.
typeCurso{id:ID!nombre:String!pipo:[Estudiante]}
Crear una función mutation:
Necesitamos crear una nueva mutation que nos permita agregar un Estudiante a un Curso.
Una vez definido el esquema, el siguiente paso es implementar el resolver que realizará las operaciones necesarias para vincular el estudiante al curso.
Conexión a la base de datos:
Asegúrate de conectar correctamente la base de datos y manejar errores potenciales.
Validación y recuperación de datos:
Verifica que el curso y el estudiante existan en la base de datos.
Actualización de los datos:
Utiliza un operador de actualización addToSet para agregar el ID del estudiante al arreglo en el documento del curso.
asyncfunctionaddPipo(_,{ courseId, personId }){try{const curso =await db.courses.findById(courseId);const persona =await db.persons.findById(personId);if(!curso ||!persona){thrownewError('El curso o la persona no existe');}await db.courses.updateOne({_id: courseId },{$addToSet:{pipo: personId }});return curso;}catch(error){console.error(error);thrownewError('Error al actualizar curso');}}
¿Cómo comprobar y probar la implementación?
Una vez implementado el resolver, es crucial verificar que todo funcione correctamente mediante pruebas.
Utiliza herramientas de prueba:
Usa aplicaciones como GraphiQL o Apollo Studio para enviar requests a tu API y confirmar que los estudiantes se agreguen correctamente al curso.
Observa los resultados directamente en la base de datos:
Verifica en tu base de datos que el campo pipo del curso contenga los IDs de los estudiantes correctos.
Asegúrate de cumplir con las expectativas del cliente:
Comprobar que el resolver devuelva el curso correctamente actualizado como respuesta de la mutation.
¿Cómo implementar y verificar mejoras?
Al incorporar este tipo de funcionalidad en una aplicación, siempre debes buscar maneras de mejorar y optimizar.
Manejo de errores más específicos:
Implementa mejores mecanismos para identificar y notificar errores específicos durante el proceso de ejecución.
Documentación y claridad del código:
Asegúrate de que tu código esté comentado y documentado adecuadamente para facilitar el mantenimiento.
Más verificaciones de datos:
Implementa validaciones adicionales para mejorar la integridad de los datos manejados.
Aprender a manejar relaciones entre tipos en GraphQL amplía las capacidades de tu API y es un paso más hacia la construcción de sistemas robustos y flexibles. ¡Sigue explorando y experimentando para perfeccionar tus habilidades!
podrian poner un ejemplo para poder ver la diferencia??
Otra cosa que podriamos hacer para poder tener un mejor codigo es abstraer la logica de la base de datos en un archivo por aparte y unicamente a los queries y mutations exponerles una interfaz por la cual hagan los inserts, updates, etc... También si la abstraemos hacemos que los archivos no sepan que base de datos se este usando y asi hacer algo mas mantenible.
Les dejo mi codigo:
Hey ! realmente no es asi , $addToSet solo creara el arreglo con el valor si el campo que le indicamos no existe en el documento. si el campo existe y no es un arreglo tirara un error
.
Escribi un Aporte al respecto, tambien por si quieren mirar les dejo la Documentacion
esos metodos updateOne, findOne, insertOne, son de mongo, pero si quisiera utilizar los de otro tipo de base de datos como mysql o postgres utilizaría los conocidos Select, Insert, Update, Delete...?
Pude implementar los nested types, agregando un proveedor de información a los datos de un malware.
addProvider:async(root,{ malwareID, companyID })=>{let db
let malware
let provider
try{ db =awaitconnectDb() malware =await db.collection('Malwares').findOne({_id:ObjectID(malwareID)}) provider =await db.collection('Companies').findOne({_id:ObjectID(companyID)})if(!malware ||!provider)thrownewError('La empresa que provee información o el malware no existe')await db.collection('Malwares').updateOne({_id:ObjectID(malwareID)},{$addToSet:{info_provider:ObjectID(companyID)}})}catch(error){errorHandler(error)}return malware
}
Un pequeño detalle y esque el operador $addToSet Busca el campo y si no es un arreglo , va a fallar o tirar un error , no va a convertir el campo a un arreglo. caso contrario que si cuando buscara el campo y este campo simplemente no existe ahi si creara el campo y un arreglo con el valor que le dijimos que insertara.
.
si el campo si es un arreglo , buscara que no exista el valor que va a insertar y si efectivamente no existe ahi añadira el valor . si el valor ya existe simplemente no hara nada , no agregara un duplicado.
.
Lo dice en la documentacion
{
"errors": [
{
"message": "Must provide query string."
}
]
}
Me sale este error cada vez que entro al link que programamos en el index como se soluciona? no he podido
Solo agrega un query en la caja de texto, no es un error fatal, solo que al iniciar graphiql hace un request con el query dentro del editor, si este esta vacio, pues responde con ese error
hmm recienteme habia visto lo de SOLID y creo que en la DB seria excelente aplicar el singleton, así cada vez que se llama la db te devuelve la instancia en memoria y no te vuelve a crear otra.
😁 Una de las cosas que me costó cuando trate de implementar GraphQL fue el performance, sobre todo en este tema cuando haces cargas de datos de objetos relacionados, y aun más si es sobre base de datos relacionarles, así que me cree esta función que me ayudo a suplir este problema
const{City}=require('../db/');const{ fields }=require('../util');constcities=async(resolve, root, args, context, info)=>{const related =fields(context).includes('related');let query =City.query().whereNull('city_id'); query = related ? query.eager('related'): query;return query;};module.exports= cities;
de esta forma solo cargo las relaciones solo si él usuario lo solicito.
Si alguien conoce una mejor forma de hacerlo 🤓 compártalo
Nested Types
Modificamos el schema:
typeCourse{_id:ID!title:String!teacher:Stringdescription:String!topic:Stringpeople:[Student]}typeMutation{"Agrega una persona a un curso"addPeople(courseID:ID!,personID:ID!):Course}
Añadimos la función a mutations.js:
addPeople:async(root,{ courseID, personID })=>{let db
let person
let course
try{ db =awaitconnectDb() course =await db.collection('courses').findOne({_id:ObjectId(courseID)}) person =await db.collection('students').findOne({_id:ObjectId(personID)})if(!course ||!person){thrownewError('La persona o el curso no existe')}await db.collection('courses').updateOne({_id:ObjectId(courseID)},{$addToSet:{people: personID }})}catch(error){console.error(error)}return course
}
Y ahora podemos insertar IDs de estudiantes a los cursos:
type Mutation{"Crea un curso"createCourse(input:CourseInput!):Course"Edita un curso"editCourse(_id:ID!,input:CourseEditInput):Course"Borrar un curso"deleteCourse(_id:ID!):String"Crea un estudiante"createStudent(input:StudentInput!):Student"Edita un estudiante"editStudent(_id:ID!,input:StudentEditInput):Student"Borra un estudiante"deleteStudent(_id:ID!):String"Agrega una persona a un curso"addPeople(courseID:ID!,personID:ID!):Course}
mutations.js
addPeople:async(root,{ courseID, personID })=>{let db
let person
let course
try{ db =awaitconnectDb() course =await db.collection('courses').findOne({_id:ObjectId(courseID)}) person =await db.collection('students').findOne({_id:ObjectId(personID)})if(!course ||!person)thrownewError('La persona o el curso no existe')await db.collection('courses').updateOne({_id:ObjectId(courseID)},{$addToSet:{people:ObjectId(personID)}})}catch(error){console.error(error)}return course
}
Para organizar mi codigo lo hice de la siguiente manera, espero les sriva. :D
Conexion con la base de datos
'use stric'const{MongoClient}=require('mongodb')classConexion{constructor(){this.mongoConnection()}mongoConnection(){MongoClient.connect( process.env.CONECTION_DB,{useNewUrlParser:true,useUnifiedTopology:true,}).then(client=>{this.db= client.db('code-rockDB')console.log('Connected to Database')}).catch(err=>console.log('Error para conectar Database',err))}}module.exports=newConexion()