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.
Define
Userconid,name,emailyactive.Implementa crear y leer con email único.
Actualiza solo los campos permitidos y conserva el identificador.
Cambia
activeafalseen 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.

Mapa de prácticaEl 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ón | Mutation GraphQL | Acción en Resolver |
|---|---|---|
| Create | createUser(input) | Valida email, hashea contraseña, crea en DB |
| Read | user(id), users | Consulta DB, filtra por permisos |
| Update | updateUser(id, input) | Valida cambios, aplica parcialmente |
| Delete | deleteUser(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
createUserretornaUser!, 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
@authpara 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
- Puedo definir tipos GraphQL (Input, Enum, Type) para todas las operaciones CRUD de usuario
- Implemento resolvers que validan lógica de negocio además de tipos GraphQL
- Uso context (dataSources) para acceder a base de datos o servicios externos
- Manejo errores con mensajes claros y códigos específicos
- Implemento soft delete en lugar de borrado físico cuando es apropiado
- Protejo mutaciones con autenticación y autorización
- 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.
- 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).
- Operación Create: Implementa la mutación
createUsercon 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. - Operación Read: Crea queries
user(id: ID!)yusersque retornen usuarios. Añade filtro opcional por role en la queryusers. - 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. - Operación Delete: Crea
deleteUser(id: ID!)que realice soft delete (cambie status a INACTIVE) en lugar de borrar físicamente. - 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.