Creación de productos con mutations e inputs en GraphQL

Clase 13 de 24Curso de GraphQL con Node.js

Contenido del curso

Resumen

Crear, actualizar o eliminar datos en una API de GraphQL requiere un mecanismo distinto al de las consultas: las mutations. Junto con ellas aparece el concepto de input, una pieza fundamental del sistema de tipos que permite agrupar argumentos de entrada de forma limpia y escalable. A continuación se explica cómo implementar una mutation para crear un producto, conectarla a la capa de servicios y lograr persistencia real en una base de datos PostgreSQL.

¿Qué son las mutations y en qué se diferencian de las queries?

Dentro del esquema de GraphQL existen dos types reservados principales. El type Query agrupa todas las operaciones de lectura, mientras que el type Mutation agrupa las operaciones que modifican el estado: crear, actualizar o eliminar registros [0:30]. Ambos comparten la misma estructura sintáctica, pero su propósito es radicalmente distinto.

Al definir una mutation en el schema, se declara una función —por ejemplo addProduct— que recibe argumentos y retorna un tipo concreto. En este caso retorna un Product que puede ser nulo si alguna regla de validación lo impide, como la duplicidad de nombres [1:05].

¿Por qué usar inputs en lugar de argumentos en línea?

Cuando una mutation necesita pocos parámetros —nombre, precio, descripción, imagen— es posible declararlos directamente en la firma de la función. Sin embargo, en escenarios reales puede haber veinte o más argumentos (información de sensores, por ejemplo), y la legibilidad se deteriora rápidamente [1:45].

Aquí entran los inputs, la forma que ofrece GraphQL para agrupar varios argumentos en un solo objeto. En otros contextos se conocen como DTO (Data Transfer Objects), es decir, objetos de transferencia de información [2:15]. La palabra reservada input precede al nombre del tipo:

graphql input CreateProductDTO { name: String! price: Int! description: String image: String }

Después se conecta al mutation asignándolo como tipo del parámetro dto:

graphql type Mutation { addProduct(dto: CreateProductDTO!): Product }

De esta manera el esquema queda más fácil de leer, mantener y extender sin modificar la firma de la función [2:50].

¿Cómo se implementa el resolver de una mutation?

El resolver es la función que ejecuta la lógica de negocio. Mediante destructuring se extrae el dto de los argumentos entrantes y se pasa directamente al servicio de creación [3:25]:

javascript async function addProduct(_, { dto }) { const newProduct = await service.create(dto); return newProduct; }

Esta función se exporta y se registra en el índice de resolvers, pero no dentro del scope de Query, sino en un scope separado llamado Mutation [3:50]:

javascript const resolvers = { Query: { ... }, Mutation: { addProduct, }, };

Como el nombre de la función coincide con el del esquema, basta con enviar la referencia sin necesidad de crear un alias.

¿Cómo se prueba una mutation en el playground?

El playground de GraphQL permite abrir múltiples tabs: uno para consultas y otro para mutations [4:20]. Para ejecutar una mutation se antepone la palabra reservada mutation:

graphql mutation { addProduct(dto: { name: "nuevoProducto" description: "charla" price: 100 image: "url-imagen" }) { id name description } }

Visualmente el playground distingue las operaciones: las queries aparecen con una letra azul y las mutations con una M naranja [5:10].

¿Qué ocurre cuando falta un campo obligatorio en el servicio?

Al ejecutar la mutation sin incluir un categoryId, el servicio devuelve el error "category not found" [5:30]. Esto sucede porque la capa de servicios —reutilizada también por la REST API— valida que cada producto pertenezca a una categoría existente. El mismo código se ejecuta sin duplicación, lo que demuestra la ventaja de compartir lógica entre GraphQL y REST [5:55].

La solución consiste en añadir el campo al input:

graphql input CreateProductDTO { name: String! price: Int! description: String image: String categoryId: ID! }

Con ese ajuste, al enviar categoryId: 1 la mutation se ejecuta correctamente y el nuevo producto queda persistido en PostgreSQL [6:30]. Al regresar al tab de consulta y solicitar la lista de productos, el registro recién creado aparece al final con todos sus campos.

Dominar mutations e inputs es esencial para construir APIs de GraphQL completas. Si ya estás experimentando con el playground, comparte qué validaciones adicionales implementarías en tus DTOs.