Concepto clave
Los resolvers son funciones que se ejecutan para obtener los datos solicitados en una consulta GraphQL. Piensa en ellos como los "camareros" de tu API: cuando un cliente (frontend) pide un plato específico (campo de datos), el camarero va a la cocina (base de datos, servicio externo, etc.) y trae exactamente lo que se solicitó.
En GraphQL con Apollo Server, cada campo definido en tu schema necesita un resolver correspondiente. La magia está en que GraphQL resuelve los campos de forma jerárquica: si consultas usuario { nombre posts { titulo } }, primero se ejecuta el resolver para usuario, luego para nombre (que suele ser simple), y finalmente para posts (que puede requerir otra llamada a base de datos).
Un resolver recibe cuatro argumentos:parent(datos del nivel superior),args(argumentos de la consulta),context(información compartida como autenticación), yinfo(detalles de la consulta).
Cómo funciona en la práctica
Imagina que tienes un schema GraphQL para una aplicación de blog. Primero, defines los tipos en tu schema:
type Post {
id: ID!
titulo: String!
contenido: String!
autor: Usuario!
}
type Usuario {
id: ID!
nombre: String!
email: String!
posts: [Post!]!
}
type Query {
posts: [Post!]!
usuario(id: ID!): Usuario
}Luego, implementas los resolvers. Aquí un ejemplo paso a paso:
- Crea un archivo
resolvers.jscon un objeto que mapee los tipos y queries. - Para el query
posts, define una función que devuelva datos (por ahora, datos simulados). - Para el campo
autorenPost, usa el argumentoparentpara acceder al ID del autor y buscar el usuario correspondiente.
Ejemplo de implementación:
const resolvers = {
Query: {
posts: () => {
// Simula datos de una base de datos
return [
{ id: '1', titulo: 'Aprendiendo GraphQL', contenido: '...', autorId: '101' },
{ id: '2', titulo: 'Next.js Tips', contenido: '...', autorId: '102' }
];
},
usuario: (parent, args, context, info) => {
const { id } = args;
// Busca usuario por ID (simulado)
return { id, nombre: 'Carlos', email: '[email protected]' };
}
},
Post: {
autor: (parent) => {
// parent contiene los datos del post, como autorId
return { id: parent.autorId, nombre: 'Autor Simulado' };
}
}
};Caso de estudio
Vamos a aplicar esto a un escenario real: una API para un e-commerce con productos y categorías. Supón que tienes este schema:
type Producto {
id: ID!
nombre: String!
precio: Float!
categoria: Categoria!
}
type Categoria {
id: ID!
nombre: String!
productos: [Producto!]!
}
type Query {
productos: [Producto!]!
categoria(id: ID!): Categoria
}Implementa resolvers que:
- Para
Query.productos, devuelva una lista de productos desde una fuente de datos (en este caso, un array en memoria). - Para
Producto.categoria, use elparentpara buscar la categoría correspondiente por ID. - Para
Categoria.productos, filtre los productos por categoría ID.
Datos de ejemplo en una tabla:
| ID Producto | Nombre | Precio | Categoria ID |
|---|---|---|---|
| P1 | Laptop | 999.99 | C1 |
| P2 | Mouse | 25.50 | C2 |
| P3 | Teclado | 45.00 | C2 |
El resolver de Producto.categoria podría verse así:
Producto: {
categoria: (parent) => {
const categorias = [
{ id: 'C1', nombre: 'Electrónica' },
{ id: 'C2', nombre: 'Accesorios' }
];
return categorias.find(cat => cat.id === parent.categoriaId);
}
}Errores comunes
- Olvidar devolver el tipo correcto: Si tu schema define
String!y devuelves un número, Apollo Server lanzará un error. Siempre valida que los datos coincidan con el schema. - No manejar argumentos en resolvers: Al definir un query con argumentos (ej:
usuario(id: ID!)), asegúrate de acceder aargs.iden el resolver. Un error típico es usarparent.idpor confusión. - Resolver asíncrono sin await: Si tu resolver hace una llamada a una base de datos (como con Prisma o MongoDB), recuerda usar
async/awaito promesas. Olvidarlo puede devolverPromiseen lugar de los datos. - Ignorar el contexto: El
contextes ideal para compartir datos como conexiones a DB o información de autenticación. No inicializarlo puede llevar a código repetitivo o inseguro. - Sobrecargar resolvers con lógica compleja: Mantén los resolvers simples; delega la lógica de negocio a servicios o capas separadas para mejorar mantenibilidad.
Checklist de dominio
- Puedo explicar qué es un resolver y su rol en GraphQL usando una analogía simple.
- He implementado al menos un query con argumentos y su resolver correspondiente.
- Sé acceder y usar los cuatro argumentos (
parent,args,context,info) en un resolver. - Puedo conectar resolvers para campos anidados (ej:
usuario.posts). - He simulado datos en memoria y los he devuelto desde un resolver.
- Reconozco y evito errores comunes como tipos incorrectos o manejo asíncrono.
- Puedo integrar un resolver básico en un proyecto Next.js con Apollo Server.
Implementa Resolvers para una API de Biblioteca
En este ejercicio, crearás resolvers simples para una API GraphQL que gestiona libros y autores en una biblioteca. Sigue estos pasos:
- Configura un proyecto Next.js con Apollo Server (usa el boilerplate del módulo anterior).
- Define el siguiente schema GraphQL en tu archivo
schema.js:type Libro { id: ID! titulo: String! autor: Autor! año: Int! } type Autor { id: ID! nombre: String! libros: [Libro!]! } type Query { libros: [Libro!]! autor(id: ID!): Autor } - Crea un archivo
resolvers.jse implementa los resolvers para:Query.libros: Devuelve un array con al menos 3 libros simulados (ej: datos en memoria).Query.autor: Acepta un argumentoidy devuelve un autor simulado basado en ese ID.Libro.autor: Usa elparentpara buscar y devolver el autor correspondiente al libro.Autor.libros: Filtra y devuelve los libros escritos por ese autor.
- Integra los resolvers en tu Apollo Server y prueba las consultas en GraphQL Playground (ej:
{ libros { titulo autor { nombre } } }). - Asegúrate de que los tipos devueltos coincidan con el schema y maneja los argumentos correctamente.
Datos de ejemplo para empezar:
- Libros: [{ id: 'L1', titulo: 'Cien años de soledad', autorId: 'A1', año: 1967 }, { id: 'L2', titulo: 'Rayuela', autorId: 'A2', año: 1963 }]
- Autores: [{ id: 'A1', nombre: 'Gabriel García Márquez' }, { id: 'A2', nombre: 'Julio Cortázar' }]
- Usa el argumento 'parent' en el resolver de Libro.autor para acceder a autorId y buscar en el array de autores.
- En Query.autor, el argumento 'id' está en args, no en parent. Asegúrate de extraerlo con const { id } = args.
- Para Autor.libros, puedes filtrar el array de libros comparando autorId con el id del autor desde parent.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.