Práctica: Desarrollar un Sistema de Usuarios con CRUD

Lectura
30 min~5 min lectura

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.