Guía de GraphQL
Guía de GraphQL
Sección titulada «Guía de GraphQL»Aprende a construir APIs GraphQL poderosas con Veloce-TS v0.2.6.
Tabla de Contenidos
Sección titulada «Tabla de Contenidos»- Resumen
- Configuración
- Resolvers
- Decoradores
- Generación de Esquemas
- Características Avanzadas
- Ejemplos
Resumen
Sección titulada «Resumen»Veloce-TS proporciona soporte completo de GraphQL con:
- Generación Automática de Esquemas a partir de tipos TypeScript
- Resolvers basados en Decoradores para código limpio y declarativo
- Integración Zod para validación y seguridad de tipos
- Argumentos con Seguridad de Tipos con validación automática
- Playground e Introspección para desarrollo
Configuración
Sección titulada «Configuración»1. Instalar Plugin GraphQL
Sección titulada «1. Instalar Plugin GraphQL»import { VeloceTS } from 'veloce-ts';
const app = new VeloceTS();
app.install('graphql', { path: '/graphql', playground: true, introspection: true});2. Opciones de Configuración GraphQL
Sección titulada «2. Opciones de Configuración GraphQL»app.install('graphql', { path: '/graphql', // Ruta del endpoint GraphQL playground: true, // Habilitar GraphQL Playground introspection: true, // Habilitar introspección de esquema cors: true, // Habilitar CORS context: (req) => ({ // Contexto personalizado user: req.user, db: database })});Resolvers
Sección titulada «Resolvers»Resolver Básico
Sección titulada «Resolver Básico»import { Resolver, GQLQuery, Arg } from 'veloce-ts';import { z } from 'zod';
const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email()});
@Resolver()export class UserResolver {
@GQLQuery() async users(): Promise<User[]> { return await userService.findAll(); }
@GQLQuery() async user(@Arg('id', z.string()) id: string): Promise<User | null> { return await userService.findById(id); }
@GQLQuery() async userByName(@Arg('name', z.string()) name: string): Promise<User[]> { return await userService.findByName(name); }}Mutaciones
Sección titulada «Mutaciones»import { GQLMutation, Arg, Body } from 'veloce-ts';
const CreateUserSchema = z.object({ name: z.string().min(1), email: z.string().email(), password: z.string().min(6)});
@Resolver()export class UserResolver {
@GQLMutation() async createUser(@Arg('input', CreateUserSchema) input: z.infer<typeof CreateUserSchema>): Promise<User> { const hashedPassword = await bcrypt.hash(input.password, 10);
const user = await userService.create({ ...input, password: hashedPassword });
return user; }
@GQLMutation() async updateUser( @Arg('id', z.string()) id: string, @Arg('input', CreateUserSchema.partial()) input: Partial<z.infer<typeof CreateUserSchema>> ): Promise<User> { return await userService.update(id, input); }
@GQLMutation() async deleteUser(@Arg('id', z.string()) id: string): Promise<boolean> { await userService.delete(id); return true; }}Suscripciones
Sección titulada «Suscripciones»import { GQLSubscription, Arg } from 'veloce-ts';
@Resolver()export class NotificationResolver {
@GQLSubscription() async userNotifications(@Arg('userId', z.string()) userId: string): AsyncIterable<Notification> { // Retornar un iterador asíncrono para notificaciones en tiempo real return notificationService.getUserNotifications(userId); }
@GQLSubscription() async taskUpdates(@Arg('taskId', z.string()) taskId: string): AsyncIterable<TaskUpdate> { return taskService.getTaskUpdates(taskId); }}Decoradores
Sección titulada «Decoradores»Decoradores GraphQL
Sección titulada «Decoradores GraphQL»| Decorador | Propósito | Ejemplo |
|---|---|---|
@Resolver() | Marca una clase como resolver GraphQL | @Resolver() |
@GQLQuery() | Define una consulta GraphQL | @GQLQuery() |
@GQLMutation() | Define una mutación GraphQL | @GQLMutation() |
@GQLSubscription() | Define una suscripción GraphQL | @GQLSubscription() |
@Arg(name, schema) | Define argumentos GraphQL | @Arg('id', z.string()) |
Manejo Avanzado de Argumentos
Sección titulada «Manejo Avanzado de Argumentos»import { Arg } from 'veloce-ts';
const PaginationSchema = z.object({ page: z.number().min(1).default(1), limit: z.number().min(1).max(100).default(10), sortBy: z.enum(['name', 'email', 'createdAt']).default('createdAt'), sortOrder: z.enum(['asc', 'desc']).default('desc')});
const FilterSchema = z.object({ name: z.string().optional(), email: z.string().email().optional(), role: z.enum(['admin', 'user', 'guest']).optional()});
@Resolver()export class UserResolver {
@GQLQuery() async users( @Arg('pagination', PaginationSchema) pagination: z.infer<typeof PaginationSchema>, @Arg('filters', FilterSchema) filters: z.infer<typeof FilterSchema> ): Promise<{ users: User[]; total: number; page: number }> { const result = await userService.findWithPagination(pagination, filters);
return { users: result.users, total: result.total, page: pagination.page }; }}Generación de Esquemas
Sección titulada «Generación de Esquemas»Conversión Automática de Tipos
Sección titulada «Conversión Automática de Tipos»Veloce-TS convierte automáticamente esquemas Zod a tipos GraphQL:
// Esquema Zodconst UserSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email(), age: z.number().int().min(0), isActive: z.boolean(), createdAt: z.date(), tags: z.array(z.string())});
// Genera automáticamente el tipo GraphQL:// type User {// id: String!// name: String!// email: String!// age: Int!// isActive: Boolean!// createdAt: DateTime!// tags: [String!]!// }Tipos Escalares Personalizados
Sección titulada «Tipos Escalares Personalizados»import { GraphQLScalarType } from 'graphql';
const DateScalar = new GraphQLScalarType({ name: 'Date', description: 'Tipo escalar personalizado de fecha', serialize: (value: Date) => value.toISOString(), parseValue: (value: string) => new Date(value), parseLiteral: (ast) => { if (ast.kind === Kind.STRING) { return new Date(ast.value); } return null; }});
// Registrar escalar personalizadoapp.install('graphql', { path: '/graphql', scalars: { Date: DateScalar }});Características Avanzadas
Sección titulada «Características Avanzadas»Integración de Autenticación
Sección titulada «Integración de Autenticación»import { Auth, CurrentUser } from 'veloce-ts';
@Resolver()export class UserResolver {
@GQLQuery() @Auth() // Requerir autenticación async me(@CurrentUser() user: any): Promise<User> { return user; }
@GQLMutation() @Auth() async updateProfile( @CurrentUser() currentUser: any, @Arg('input', UpdateProfileSchema) input: any ): Promise<User> { // Solo permitir que los usuarios actualicen su propio perfil if (currentUser.id !== input.userId) { throw new ForbiddenException('No puedes actualizar otros usuarios'); }
return await userService.update(currentUser.id, input); }}Manejo de Errores
Sección titulada «Manejo de Errores»import { NotFoundException, ValidationException } from 'veloce-ts';
@Resolver()export class UserResolver {
@GQLQuery() async user(@Arg('id', z.string()) id: string): Promise<User> { const user = await userService.findById(id);
if (!user) { throw new NotFoundException(`Usuario con id ${id} no encontrado`); }
return user; }
@GQLMutation() async createUser(@Arg('input', CreateUserSchema) input: any): Promise<User> { try { return await userService.create(input); } catch (error) { if (error.code === 'DUPLICATE_EMAIL') { throw new ValidationException('El email ya existe'); } throw error; } }}Integración DataLoader
Sección titulada «Integración DataLoader»import DataLoader from 'dataloader';
@Resolver()export class UserResolver { private userLoader = new DataLoader(async (ids: string[]) => { const users = await userService.findByIds(ids); return ids.map(id => users.find(user => user.id === id) || null); });
@GQLQuery() async users(): Promise<User[]> { const userIds = await userService.getAllUserIds(); return await this.userLoader.loadMany(userIds); }}Ejemplos
Sección titulada «Ejemplos»API Completa de Gestión de Usuarios
Sección titulada «API Completa de Gestión de Usuarios»import { Resolver, GQLQuery, GQLMutation, Arg } from 'veloce-ts';import { z } from 'zod';
const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.string().email(), role: z.enum(['admin', 'user', 'guest']), createdAt: z.date()});
const CreateUserInput = z.object({ name: z.string().min(1), email: z.string().email(), role: z.enum(['admin', 'user', 'guest']).default('user')});
const UpdateUserInput = CreateUserInput.partial();
@Resolver()export class UserResolver {
@GQLQuery() async users(): Promise<User[]> { return await userService.findAll(); }
@GQLQuery() async user(@Arg('id', z.string()) id: string): Promise<User | null> { return await userService.findById(id); }
@GQLMutation() async createUser(@Arg('input', CreateUserInput) input: z.infer<typeof CreateUserInput>): Promise<User> { return await userService.create(input); }
@GQLMutation() async updateUser( @Arg('id', z.string()) id: string, @Arg('input', UpdateUserInput) input: z.infer<typeof UpdateUserInput> ): Promise<User> { return await userService.update(id, input); }
@GQLMutation() async deleteUser(@Arg('id', z.string()) id: string): Promise<boolean> { await userService.delete(id); return true; }}Ejemplos de Consultas GraphQL
Sección titulada «Ejemplos de Consultas GraphQL»# Consultar todos los usuariosquery GetUsers { users { id name email role createdAt }}
# Consultar usuario específicoquery GetUser($id: String!) { user(id: $id) { id name email role }}
# Mutación para crear usuariomutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name email role createdAt }}
# Mutación para actualizar usuariomutation UpdateUser($id: String!, $input: UpdateUserInput!) { updateUser(id: $id, input: $input) { id name email role }}
# Mutación para eliminar usuariomutation DeleteUser($id: String!) { deleteUser(id: $id)}Ejemplo de Variables
Sección titulada «Ejemplo de Variables»{ "id": "user-123", "input": { "name": "Juan Pérez", "email": "juan@ejemplo.com", "role": "user" }}Mejores Prácticas
Sección titulada «Mejores Prácticas»1. Usar Zod para Validación
Sección titulada «1. Usar Zod para Validación»// Bueno: Tipado fuerte con Zodconst CreateUserSchema = z.object({ name: z.string().min(1).max(100), email: z.string().email(), age: z.number().int().min(0).max(150)});
@GQLMutation()async createUser(@Arg('input', CreateUserSchema) input: z.infer<typeof CreateUserSchema>) { // input está completamente tipado y validado}2. Manejar Errores con Gracia
Sección titulada «2. Manejar Errores con Gracia»@GQLQuery()async user(@Arg('id', z.string()) id: string): Promise<User> { try { const user = await userService.findById(id); if (!user) { throw new NotFoundException('Usuario no encontrado'); } return user; } catch (error) { // Registrar error para depuración console.error('Error al obtener usuario:', error); throw error; }}3. Usar DataLoaders para Rendimiento
Sección titulada «3. Usar DataLoaders para Rendimiento»// Evitar consultas N+1@Resolver()export class PostResolver { private userLoader = new DataLoader(async (userIds: string[]) => { return await userService.findByIds(userIds); });
@GQLQuery() async posts(): Promise<Post[]> { const posts = await postService.findAll();
// Cargar usuarios eficientemente const users = await this.userLoader.loadMany( posts.map(post => post.authorId) );
return posts.map(post => ({ ...post, author: users.find(user => user?.id === post.authorId) })); }}Próximos Pasos
Sección titulada «Próximos Pasos»- Ejemplo TaskMaster - Ver GraphQL en acción
- Guía de Autenticación - Asegurar tu API GraphQL
- Guía de WebSockets - Suscripciones en tiempo real
- Guía de Pruebas - Probar tus resolvers GraphQL