Saltar al contenido

Lección 3 de 20 · Lectura y práctica

Crear Resolvers Simples para Consultas

Tipo de lección: VideoConcepto claveLos 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.

Del concepto a la práctica

Taller de esta lección

Un resolver de consulta debe traducir argumentos a una selección determinista de datos. Para una búsqueda por identificador, la responsabilidad concreta es leer args.id, localizar un registro y devolver null si no existe; no debe ignorar el argumento ni devolver toda la colección.

Ejemplo resuelto

Con datos locales:

ts
const rooms = [{ id: "r1", name: "Norte" }, { id: "r2", name: "Sur" }];
const resolvers = { Query: { room: (_p: unknown, { id }: { id: string }) => rooms.find(r => r.id === id) ?? null } };

room(id: "r2") produce { "id": "r2", "name": "Sur" }.

  1. Define el tipo Room y el campo room(id: ID!): Room.

  2. Conserva los datos de prueba en una colección pequeña.

  3. Extrae el identificador desde args.

  4. Prueba un identificador existente y otro ausente.

Error frecuente: comparar parent.id en el resolver de Query.room. Corrección: el nivel raíz no tiene parent útil; el dato solicitado está en args.

Pruébalo

Agrega capacity: Int! y devuelve la capacidad de cada sala sin modificar la búsqueda.

Comprueba tu resultado

room(id: "r1") devuelve solo la sala Norte, el identificador desconocido devuelve null y pedir un campo inexistente falla durante la validación.

Define también el contrato de sala

Usa type Room { id: ID!, name: String!, capacity: Int! } y type Query { room(id: ID!): Room }; agrega capacity=4 para r1 y capacity=8 para r2 a los datos. Selecciona { room(id: "r2") { id name capacity } }: obtienes Sur y 8. No declares capacity no nulo antes de completar todos los registros. room(r9) devuelve null porque el campo raíz es nullable; la respuesta de un objeto inexistente no equivale a una operación con un campo desconocido.

Navegador, API y base de datos conectados por solicitudes, consultas y respuestas en sentidos opuestos.
CursaloMapa de práctica
Del navegador a los datos

El navegador envía una solicitud a la API. El servidor valida la operación y consulta los datos; después devuelve una respuesta que el navegador puede mostrar. La base de datos se comunica con el servidor y no directamente con el navegador.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Tipo de lección: Video

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), y info (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:

  1. Crea un archivo resolvers.js con un objeto que mapee los tipos y queries.
  2. Para el query posts, define una función que devuelva datos (por ahora, datos simulados).
  3. Para el campo autor en Post, usa el argumento parent para 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: 'carlos@ejemplo.com' };
    }
  },
  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 el parent para buscar la categoría correspondiente por ID.
  • Para Categoria.productos, filtre los productos por categoría ID.

Datos de ejemplo en una tabla:

ID ProductoNombrePrecioCategoria ID
P1Laptop999.99C1
P2Mouse25.50C2
P3Teclado45.00C2

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 a args.id en el resolver. Un error típico es usar parent.id por 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/await o promesas. Olvidarlo puede devolver Promise en lugar de los datos.
  • Ignorar el contexto: El context es 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

  1. Puedo explicar qué es un resolver y su rol en GraphQL usando una analogía simple.
  2. He implementado al menos un query con argumentos y su resolver correspondiente.
  3. Sé acceder y usar los cuatro argumentos (parent, args, context, info) en un resolver.
  4. Puedo conectar resolvers para campos anidados (ej: usuario.posts).
  5. He simulado datos en memoria y los he devuelto desde un resolver.
  6. Reconozco y evito errores comunes como tipos incorrectos o manejo asíncrono.
  7. 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:

  1. Configura un proyecto Next.js con Apollo Server (usa el boilerplate del módulo anterior).
  2. 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
    }
  3. Crea un archivo resolvers.js e implementa los resolvers para:
    • Query.libros: Devuelve un array con al menos 3 libros simulados (ej: datos en memoria).
    • Query.autor: Acepta un argumento id y devuelve un autor simulado basado en ese ID.
    • Libro.autor: Usa el parent para buscar y devolver el autor correspondiente al libro.
    • Autor.libros: Filtra y devuelve los libros escritos por ese autor.
  4. Integra los resolvers en tu Apollo Server y prueba las consultas en GraphQL Playground (ej: { libros { titulo autor { nombre } } }).
  5. 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' }]
Pistas
  • 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.

Sigue con GraphQL Profesional con Next.js y Apollo Server: APIs Tipadas y Suscripciones

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.