Curso de GraphQL con Node.js

Cómo separar resolvers GraphQL por entidad

Curso de GraphQL con Node.js

Contenido del curso

Cómo separar resolvers GraphQL por entidad

Resumen

Organizar resolvers en GraphQL por responsabilidad es una práctica que mejora la escalabilidad y el mantenimiento de tu API. Si los acumulas todos en un solo archivo, tarde o temprano vas a terminar con un monstruo difícil de leer. Aquí aprendes a dividirlos por dominio, igual que harías con las rutas en una Rest API.

¿Por qué dividir los resolvers en archivos separados?

Cuando creces una API con productos, categorías, órdenes y customers, tener todos los resolvers en un solo archivo rompe el principio de una sola responsabilidad. La idea es replicar lo que ya haces con tu routing en Rest: un archivo product.router se encarga solo de productos, otro de órdenes, otro de customers.

En GraphQL aplicas la misma lógica creando, por ejemplo, un archivo product.resolvers que agrupe todas las funciones relacionadas con la entidad producto: obtener uno, obtener varios, crear, actualizar, eliminar.

¿Qué es un resolver en GraphQL? Es la función que ejecuta la lógica detrás de una query o mutation. Recibe parámetros como el contexto y los argumentos enviados desde el cliente, y devuelve los datos que coinciden con el esquema.

¿Cómo crear un archivo de resolvers por entidad?

La estructura parte de un archivo dedicado por entidad. Dentro defines funciones tipo arrow function que representan cada operación.

¿Qué estructura tiene un archivo product.resolvers?

Dentro del archivo declaras cada función con su firma completa. Un resolver recibe un primer parámetro que normalmente se ignora y un segundo parámetro con los argumentos enviados desde la query.

  • getProduct: retorna un producto individual usando un id recibido por argumentos.
  • getProducts: retorna una lista de productos, por ahora un array vacío hasta conectar con la capa de servicios.
  • Funciones futuras como agregar, actualizar o eliminar producto.

Al final del archivo usas module.exports para exponer las funciones que este módulo se encarga de manejar. Así, todo lo relacionado con producto queda aislado y mantenible.

¿Cómo se reciben los argumentos en un resolver?

Puedes acceder a los argumentos de dos formas. La clásica usa args.id, donde args es el segundo parámetro de la función. La más limpia usa destructuring directamente en la firma para extraer solo el campo que necesitas.

js const getProduct = (_, { id }) => { return product; };

Este patrón hace el código más legible porque sabes de un vistazo qué argumentos consume cada resolver.

¿Cómo unir los resolvers en un archivo central?

Para no recargar el index.js, creas un archivo resolvers.js que funciona como directorio principal. Ahí importas cada conjunto de funciones por entidad y las ensamblas en el objeto que GraphQL espera.

js const { getProduct, getProducts } = require('./product.resolvers');

module.exports = { Query: { product: getProduct, products: getProducts, }, };

Este archivo cumple el mismo papel que el index.js de las rutas en Rest: enruta hacia el módulo correcto sin contener la lógica.

¿Por qué el nombre del resolver debe coincidir con el del esquema? GraphQL hace match entre el campo declarado en el esquema y la clave del objeto de resolvers. Si tu esquema define product, la clave en Query debe llamarse product, aunque la función interna se llame getProduct.

¿Cómo nombrar queries siguiendo convenciones de GraphQL?

En la mayoría de servidores GraphQL no se usan nombres tipo getProduct o getProducts en el esquema. Se prefiere nombrar las queries con la entidad directamente, ya que el contexto deja claro que es una lectura.

  • product: para obtener un producto individual.
  • products o allProducts: para obtener la lista completa.
  • Ejemplo real: la API pública de Star Wars expone allFilms para listar películas.

En el esquema, product recibe un ID! obligatorio porque sin él no puedes ubicar el recurso. El retorno, en cambio, sí permite null, porque el id solicitado podría no existir en la base de datos.

graphql type Query { product(id: ID!): Product products: [Product!]! }

La notación [Product!]! indica que el array no puede ser null y tampoco puede contener elementos null en su interior.

¿Cómo probar los resolvers en el playground?

Una vez configurado el esquema y los resolvers, abres el playground y lanzas la query seleccionando los campos que necesitas del objeto retornado.

graphql query { product(id: "1") { id name } }

Si pasas un id distinto, ese valor llega como argumento al resolver. Para products, como retorna un array de objetos, también debes seleccionar atributos específicos como id y name. Por ahora el resolver devuelve un array vacío, hasta conectar con la capa de servicios que gestiona la base de datos.

Con esta separación tienes una arquitectura limpia donde cada archivo cumple una sola responsabilidad y tu index.js queda mínimo. ¿Cómo organizas tú los resolvers en tus proyectos? Cuéntalo en los comentarios.