La elección entre GraphQL y REST puede definir el éxito de tu API. Después de implementar ambas tecnologías en diversos proyectos, compartamos las lecciones aprendidas y los patrones que realmente funcionan en producción.
Más Allá de los Conceptos Básicos
Olvidemos la típica comparación superficial. Veamos implementaciones reales:
REST: Implementación Moderna
// src/controllers/ProductController.ts
import { Controller, Get, Query, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor } from '@nestjs/cache-manager';
@Controller('products')
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get()
@UseInterceptors(CacheInterceptor)
async getProducts(
@Query('include') include: string[],
@Query('fields') fields: string[]
) {
const products = await this.productService.findAll({
include,
select: fields
});
return {
data: products,
meta: {
total: products.length,
cached: true
}
};
}
}
Este enfoque REST moderno ofrece:
- Campos selectivos
- Relaciones bajo demanda
- Caché integrado
- Metadata útil
GraphQL: Implementación Práctica
// src/schema/product.ts
import { Field, ObjectType, ID, Resolver, Query } from 'type-graphql';
@ObjectType()
class Product {
@Field(type => ID)
id: string;
@Field()
name: string;
@Field(type => [Category], { nullable: true })
categories?: Category[];
@Field(type => [Review], { nullable: true })
reviews?: Review[];
}
@Resolver(of => Product)
export class ProductResolver {
constructor(
private readonly productService: ProductService,
private readonly loader: DataLoader<string, Product>
) {}
@Query(returns => [Product])
async products(
@Arg('filter', { nullable: true }) filter: ProductFilter,
@Arg('sort', { nullable: true }) sort: ProductSort
): Promise<Product[]> {
return this.loader.loadMany(
await this.productService.findIds(filter, sort)
);
}
}
Ventajas de esta implementación:
- DataLoader para N+1 queries
- Tipado fuerte
- Resolvers eficientes
- Carga selectiva automática
Patrones de Optimización
// src/middleware/performance.ts
export class PerformanceMiddleware {
async handle(
request: Request,
next: NextFunction
) {
const start = performance.now();
// Tracking de campos solicitados
const fieldsRequested = this.parseFields(request);
const response = await next(request);
// Métricas de performance
metrics.record('api.response.time', {
duration: performance.now() - start,
endpoint: request.path,
fields: fieldsRequested.length
});
return response;
}
}
Este middleware nos permite:
- Medir performance real
- Identificar patrones de uso
- Optimizar endpoints populares
- Detectar problemas temprano
Caché Inteligente
// src/services/cache.ts
export class CacheService {
constructor(
private redis: Redis,
private metrics: MetricsService
) {}
async getCached<T>(
key: string,
generator: () => Promise<T>,
options: CacheOptions
): Promise<T> {
const cached = await this.redis.get(key);
if (cached) {
this.metrics.increment('cache.hit');
return JSON.parse(cached);
}
const data = await generator();
await this.redis.setex(
key,
options.ttl || 3600,
JSON.stringify(data)
);
this.metrics.increment('cache.miss');
return data;
}
}
Beneficios de este sistema:
- Caché por patrón de uso
- Invalidación inteligente
- Métricas de efectividad
- Prevención de thundering herd
Conclusiones Prácticas
Después de implementar ambas soluciones en producción:
- Usa REST cuando:
- Necesitas cache agresivo
- Tienes patrones de uso predecibles
- Requieres máxima compatibilidad
- Usa GraphQL cuando:
- Tienes datos altamente relacionados
- Necesitas flexibilidad en el cliente
- Trabajas con múltiples clientes
Recursos Recomendados
Para dominar estas tecnologías, recomiendo:
- “Production Ready GraphQL” – El mejor libro sobre GraphQL en producción, y lo mejor de todo: GRATUITO!
- “REST in Practice” – Patrones modernos de REST