Saltar al contenido

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

Práctica: Desarrollar un Sistema de Usuarios con CRUD

Concepto claveLos resolvers avanzados y mutaciones tipadas son el corazón de una API GraphQL robusta. Piensa en los resolvers como los "empleados" de tu API: cada uno tiene una tarea específica (obtener datos, crear registros,.

Del concepto a la práctica

Taller de esta lección

Un CRUD de usuarios necesita una política explícita para borrar: en una red social suele ser más seguro desactivar que eliminar físicamente, porque publicaciones y comentarios aún pueden referenciar la cuenta. El ejercicio usa active para conservar esas relaciones.

Ejemplo resuelto

Estado inicial: u1 Ana active=true, u2 Luis active=true. deactivateUser(id: "u2") devuelve Luis con active=false; la consulta pública lista solo Ana, mientras una consulta administrativa puede listar ambos.

  1. Define User con id, name, email y active.

  2. Implementa crear y leer con email único.

  3. Actualiza solo los campos permitidos y conserva el identificador.

  4. Cambia active a false en lugar de borrar el registro.

Error frecuente: reutilizar el email de una cuenta desactivada sin decidir la regla. Corrección: documenta si el email sigue reservado y haz que la validación refleje esa decisión.

Pruébalo

Desactiva u2, intenta crear otro usuario con su email y consulta usuarios activos.

Comprueba tu resultado

La lista activa contiene solo u1; la creación se rechaza: en esta práctica el email sigue reservado incluso tras desactivar, y u2 continúa disponible para resolver referencias históricas.

Política y operaciones completas

Fija los correos de práctica ana@local.test y luis@local.test; no son destinos de envío. Crear u3 con otro correo válido devuelve active=true. Leer públicamente solo devuelve id y name de cuentas activas; email se reserva a me y a la vista administrativa autorizada. Actualizar el propio name requiere sesión y conserva id/email; desactivar u2 requiere permiso explícito. Tras desactivar u2 y antes de crear u3, la lista contiene únicamente u1. Eliminar lógicamente no elimina todos los datos personales ni resuelve por sí solo una solicitud de privacidad: este ejercicio comprueba integridad de relaciones.

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

Concepto clave

Los resolvers avanzados y mutaciones tipadas son el corazón de una API GraphQL robusta. Piensa en los resolvers como los "empleados" de tu API: cada uno tiene una tarea específica (obtener datos, crear registros, actualizar información) y sigue instrucciones claras (los tipos GraphQL). Las mutaciones tipadas son como formularios de solicitud con validación incorporada: aseguran que los datos que recibes sean correctos antes de procesarlos.

En un sistema de usuarios CRUD, esto se traduce en operaciones predecibles y seguras. Imagina que estás construyendo el sistema de gestión de empleados de una empresa: necesitas crear nuevos empleados (Create), leer sus datos (Read), actualizar su información (Update) y desactivar cuentas (Delete). GraphQL con tipos garantiza que cada operación reciba exactamente lo que necesita, como un formulario que rechaza entradas incorrectas automáticamente.

Cómo funciona en la práctica

Vamos a implementar un resolver para crear usuarios. Primero, define el tipo GraphQL en tu esquema:

type Mutation {
  createUser(input: CreateUserInput!): User!
}

input CreateUserInput {
  name: String!
  email: String!
  role: UserRole!
}

enum UserRole {
  ADMIN
  EDITOR
  VIEWER
}

Luego, en tu resolver (usando Apollo Server con Next.js):

const resolvers = {
  Mutation: {
    createUser: async (_, { input }, { dataSources }) => {
      // 1. Validar datos (ej: email único)
      const existingUser = await dataSources.users.findByEmail(input.email);
      if (existingUser) throw new Error('Email ya registrado');
      
      // 2. Transformar datos si es necesario
      const userData = {
        ...input,
        createdAt: new Date().toISOString(),
        status: 'ACTIVE'
      };
      
      // 3. Persistir en base de datos
      const newUser = await dataSources.users.create(userData);
      
      // 4. Retornar según tipo User
      return newUser;
    }
  }
};

Este flujo asegura que cada creación de usuario pase por validación, transformación y persistencia, retornando un objeto tipado.

Caso de estudio

Implementemos un CRUD completo para usuarios en una plataforma de cursos online. Los usuarios tienen: nombre, email, rol (estudiante, instructor, admin) y fecha de suscripción. Usaremos una tabla para visualizar las operaciones:

OperaciónMutation GraphQLAcción en Resolver
CreatecreateUser(input)Valida email, hashea contraseña, crea en DB
Readuser(id), usersConsulta DB, filtra por permisos
UpdateupdateUser(id, input)Valida cambios, aplica parcialmente
DeletedeleteUser(id)Cambia estado a INACTIVE (soft delete)

Ejemplo de mutación de actualización tipada:

// Esquema
type Mutation {
  updateUser(id: ID!, input: UpdateUserInput!): User!
}

input UpdateUserInput {
  name: String
  email: String
  role: UserRole
}

// Resolver
updateUser: async (_, { id, input }, { dataSources }) => {
  // Solo actualiza campos proporcionados
  const updates = Object.keys(input).reduce((acc, key) => {
    if (input[key] !== undefined) acc[key] = input[key];
    return acc;
  }, {});
  
  return dataSources.users.update(id, updates);
}
Las mutaciones tipadas permiten actualizaciones parciales seguras: el cliente solo envía lo que necesita cambiar, y el validador de GraphQL rechaza datos inválidos antes de llegar al resolver.

Errores comunes

  • No validar en el resolver: Confiar solo en la validación de tipos GraphQL. Los tipos aseguran formato, pero no lógica de negocio (ej: email único). Siempre añade validación específica en el resolver.
  • Mutaciones que retornan tipos incorrectos: Si tu mutación createUser retorna User!, asegúrate de que el objeto retornado tenga todos los campos requeridos por el tipo User. Usa herramientas como GraphQL Code Generator para evitar esto.
  • Resolvers bloqueantes: Realizar operaciones síncronas pesadas en resolvers. Para operaciones como hashing de contraseñas, usa versiones asíncronas o delegar a servicios externos.
  • Exponer datos sensibles: Retornar campos como contraseñas o tokens en respuestas. Define tipos separados para input y output, o usa directivas como @auth para controlar acceso.
  • Manejo pobre de errores: Lanzar errores genéricos. Usa errores específicos de GraphQL (GraphQLError) con códigos personalizados para que el cliente pueda manejarlos apropiadamente.

Checklist de dominio

  1. Puedo definir tipos GraphQL (Input, Enum, Type) para todas las operaciones CRUD de usuario
  2. Implemento resolvers que validan lógica de negocio además de tipos GraphQL
  3. Uso context (dataSources) para acceder a base de datos o servicios externos
  4. Manejo errores con mensajes claros y códigos específicos
  5. Implemento soft delete en lugar de borrado físico cuando es apropiado
  6. Protejo mutaciones con autenticación y autorización
  7. Pruebo mis resolvers con consultas reales en GraphQL Playground

Implementa un CRUD completo de usuarios con GraphQL tipado

En este ejercicio, construirás un sistema de gestión de usuarios con operaciones CRUD completas usando GraphQL tipado con Apollo Server en Next.js.

  1. Configuración inicial: Crea un nuevo proyecto Next.js con Apollo Server. Define los tipos GraphQL para User (id, name, email, role, createdAt, status) y UserRole (ADMIN, EDITOR, VIEWER).
  2. Operación Create: Implementa la mutación createUser con input tipado. Valida que el email sea único antes de crear. Hashea la contraseña (simula con una función) y establece createdAt automáticamente.
  3. Operación Read: Crea queries user(id: ID!) y users que retornen usuarios. Añade filtro opcional por role en la query users.
  4. Operación Update: Implementa updateUser(id: ID!, input: UpdateUserInput!) que permita actualizar name, email, o role. Valida que el nuevo email (si se proporciona) no esté en uso por otro usuario.
  5. Operación Delete: Crea deleteUser(id: ID!) que realice soft delete (cambie status a INACTIVE) en lugar de borrar físicamente.
  6. Pruebas: Usa GraphQL Playground para probar todas las operaciones. Verifica que los tipos retornados coincidan con tu esquema.
Pistas
  • Usa un objeto en memoria (como un array) para simular la base de datos en este ejercicio, enfócate en la lógica de resolvers.
  • Para hashing de contraseña, puedes usar una función simple que retorne un string fijo por ahora, como 'hashed_password'.
  • Implementa validación de email único recorriendo tu array de usuarios y comparando emails.

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.