Guía de Diseño de GraphQL API
Plantilla de lineamientos internos de diseño de APIs GraphQL con estructura de schema, nombres, mutaciones, paginación, auth, rate limiting, versionado y federación.
Overview
Este documento es una plantilla lista para copiar de lineamientos internos de diseño de APIs GraphQL. Define las reglas que siguen todos los equipos que construyen servicios GraphQL — estructura del schema, nombres, diseño de queries y mutaciones, manejo de errores, autenticación, rate limiting, versionado y federación — para que todas las APIs de la organización se vean y se comporten igual.
Úsala como punto de partida para tu propio documento de diseño: copia las reglas que te sirvan, ajusta los números a tu escala y elimina lo que no aplique. Para el trasfondo y el razonamiento detrás de las decisiones de diseño del schema, lee primero la Guía Completa de Diseño de Schemas GraphQL — este documento es la capa de aplicación, no el tutorial.
Hay dos razones para estandarizar esto temprano. Primero, los clientes GraphQL se acoplan al schema con más fuerza que los clientes REST a los endpoints: los nombres de campos, la nulabilidad y las formas de paginación pasan a ser parte del contrato público. Segundo, la mayoría de los errores en GraphQL son caros de deshacer porque no hay un escape de versionado — evolucionas el schema en su lugar o rompes a los clientes.
He visto equipos posponer este documento hasta que salió el tercer servicio, y para entonces la limpieza costó más de lo que habría costado escribirlo: tres convenciones de nombres, dos estilos de paginación y una deprecación que nadie quería asumir. Escribe las reglas mientras todavía hay un solo schema al que aplicarlas.
1. Estructura del Schema
1.1 Tipos Raíz
Todo schema declara los tres tipos raíz explícitamente, aunque todavía no se usen suscripciones. Mantener subscription en el esqueleto del schema evita reestructurar archivos más adelante y documenta que las operaciones en tiempo real son parte de primera clase de la API.
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
type Query {
# Recurso individual por ID
user(id: ID!): User
node(id: ID!): Node
# Lista con paginación
users(first: Int, after: String, filter: UserFilter): UserConnection!
}
type Mutation {
# Operaciones CRUD
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(input: UpdateUserInput!): UpdateUserPayload!
deleteUser(input: DeleteUserInput!): DeleteUserPayload!
}
type Subscription {
userUpdated(id: ID!): UserUpdatePayload!
}
El campo node(id: ID!) es la convención de Relay para re-obtener cualquier objeto que exponga la interfaz Node. Es opcional, pero si usas Apollo Client o Relay en el frontend lo vas a querer — los caches normalizados lo usan para actualizar objetos en su lugar.
1.2 Reglas de Organización del Schema
Un archivo por dominio mantiene los conflictos de merge al mínimo y la propiedad clara. Cuando users.graphql y orders.graphql crecen a mil líneas, dividir por dominio es lo que permite que dos equipos trabajen sin bloquearse.
- Un archivo de schema por dominio (p. ej.,
users.graphql,orders.graphql) - Tipos compartidos en
shared.graphql - Escalares personalizados en
scalars.graphql - Ningún tipo definido en más de un archivo
- Schema con stitching o federado — nunca duplicado
Los tipos duplicados son el modo de fallo a vigilar. Si Money o Address termina definido en tres archivos, las herramientas de stitching elegirán una versión en silencio o fallarán la composición. Mantén los tipos compartidos en shared.graphql e impórtalos. He depurado dos veces la variante “dos tipos Money, uno equivocado” de este bug; ambas salieron a la luz como un cliente leyendo un precio que intercambiaba campos de moneda en silencio.
2. Reglas de Nombres
2.1 Tabla de Convenciones de Nombres
La consistencia de nombres es barata de aplicar al escribir el schema y cara de corregir después — renombrar un campo es un cambio incompatible que pasa por el ciclo de deprecación de la sección 8. La mayoría de las discusiones de schema que he mediado eran en realidad discusiones de nombres disfrazadas de técnicas, así que esta tabla existe para terminarlas antes de que empiecen.
Elemento | Convención | Ejemplo
──────────────────┼────────────────────┼──────────────────
Tipo | PascalCase | ProductOrder
Interfaz | PascalCase | Node, Ownable
Unión | PascalCase | SearchResult
Enum | PascalCase | OrderStatus
Valor de enum | UPPER_SNAKE_CASE | PENDING, SHIPPED
Campo | camelCase | createdAt
Argumento | camelCase | pageSize
Tipo input | {Type}Input | CreateUserInput
Tipo payload | {Verb}{Type}Payload | CreateUserPayload
Connection | {Type}Connection | UserConnection
Edge | {Type}Edge | UserEdge
Input de filtro | {Type}Filter | UserFilter
Input de orden | {Type}Sort | UserSort
Directiva | camelCase | @deprecated
Escalar personalizado | PascalCase | DateTime, URL
2.2 Nombres Prohibidos
- Nada de nombres genéricos:
Status,Type,Data,Result,Info— colisionan en cuanto dos dominios necesitan el mismo concepto, y de todos modos obligan a los clientes a la ambigüedad deorderStatusvspaymentStatus. - Nada de abreviaturas:
Usren lugar deUser,Orden lugar deOrder. - Nada de notación húngara:
strName,intCount. El sistema de tipos ya lleva esa información. - Nada de nombres estilo REST:
getUserById,POST /users. Los campos GraphQL describen datos, no operaciones.
La prohibición de nombres genéricos importa más de lo que parece. Un enum Status en shared.graphql parece conveniente hasta que facturación necesita PaymentStatus y envíos necesita ShipmentStatus — entonces o deprecas el enum compartido o vives con orderStatus: Status sin que nadie sepa a qué máquina de estados pertenece.
3. Diseño de Queries
3.1 Reglas de Queries
Las queries deben ser de solo lectura. Nada lo impone a nivel de protocolo — un resolver puede hacer cualquier cosa — pero es la regla que mantiene viables el caché, las queries persistidas y las estrategias de CDN. En cuanto una query escribe datos, ya no puedes reejecutarla con seguridad ni cachearla.
- Las queries deben ser de solo lectura — sin efectos secundarios
- Las queries de recurso individual aceptan el argumento
id: ID! - Las queries de lista usan el patrón Connection con
first,after,last,before - Las queries de lista aceptan un argumento
filterpara filtrar - Las queries de lista aceptan un argumento
sortpara ordenar - Ninguna query devuelve más de 100 elementos por página
- Ninguna query anida más de 10 niveles
El límite de 10 niveles de profundidad existe porque la profundidad es la forma más barata de construir una query abusiva: user { friends { friends { friends { ... } } } } crece exponencialmente. Diez niveles es generoso para UIs legítimas — la mayoría de las queries reales se quedan por debajo de seis.
type Query {
# Recurso individual
product(id: ID!): Product
productBySlug(slug: String!): Product
# Lista con paginación completa
products(
first: Int = 20
after: String
filter: ProductFilter
sort: ProductSort
): ProductConnection!
}
input ProductFilter {
category: ProductCategory
priceMin: Float
priceMax: Float
inStock: Boolean
search: String
}
input ProductSort {
field: ProductSortField!
direction: SortDirection!
}
enum ProductSortField {
NAME
PRICE
CREATED_AT
POPULARITY
}
enum SortDirection {
ASC
DESC
}
La paginación por cursor con el patrón Connection es el estándar por una razón: la paginación por offset se rompe cuando se insertan filas mientras un cliente está paginando, y obliga a la base de datos a contar y descartar filas en cada petición. Los cursores anclan la página siguiente a la última fila vista, así que las inserciones no desplazan resultados y la base de datos busca en lugar de escanear.
La contrapartida es que los cursores no permiten “saltar a la página 47”. Si tu panel de administración necesita saltos arbitrarios de página, expón paginación por offset en un campo separado solo para uso interno — no lo conviertas en el contrato público.
4. Diseño de Mutaciones
4.1 Reglas de Mutaciones
Toda mutación toma un único argumento input y devuelve un tipo payload. La convención del input único no es estética — es lo que permite añadir campos opcionales después sin romper clientes. createUser(name: String!, email: String!) se convierte en cambio incompatible el día que añades phone, mientras que createUser(input: CreateUserInput!) lo absorbe gratis.
- Todas las mutaciones toman un único argumento
input - Todas las mutaciones devuelven un tipo payload (no la entidad directamente)
- Los tipos payload incluyen la entidad mutada y el array de errores
- Los nombres de mutación empiezan con un verbo:
create,update,delete,archive,restore - Las mutaciones en lote usan plural:
createUsers,deleteProducts - Nada de éxito parcial — las mutaciones son todo o nada
type Mutation {
createOrder(input: CreateOrderInput!): CreateOrderPayload!
updateOrder(input: UpdateOrderInput!): UpdateOrderPayload!
cancelOrder(input: CancelOrderInput!): CancelOrderPayload!
archiveOrder(input: ArchiveOrderInput!): ArchiveOrderPayload!
}
input CreateOrderInput {
customerId: ID!
items: [OrderItemInput!]!
shippingAddressId: ID!
notes: String
}
type CreateOrderPayload {
order: Order
userErrors: [UserError!]!
}
type UserError {
field: String
message: String!
code: UserErrorCode!
}
enum UserErrorCode {
INVALID_INPUT
NOT_FOUND
UNAUTHORIZED
FORBIDDEN
CONFLICT
RATE_LIMITED
}
4.2 Reglas de Payload de Mutación
- Nombre del tipo payload:
{Verb}{Type}Payload - El payload incluye la entidad mutada (nullable — null en caso de error)
- El payload incluye
userErrors: [UserError!]!(array vacío en caso de éxito) - El payload puede incluir campos relacionados (p. ej.,
orderCountdespués decreateOrder) - Nada de campo genérico
success: Boolean— la presencia de la entidad indica éxito
El patrón userErrors gestiona los errores que el array errors de nivel superior de GraphQL no puede: fallos esperados de negocio que el usuario puede corregir. Un error de nivel superior significa “algo salió mal”; un userError significa “tu entrada fue rechazada, aquí está qué campo y por qué”. Los clientes muestran los userErrors en línea sobre el formulario que envió la mutación — por eso field está ahí.
success: Boolean está prohibido porque añade un estado que el cliente tiene que comprobar sin aportar información. order != null ya te dice que la mutación tuvo éxito, y que userErrors esté vacío lo confirma. Dos señales para el mismo hecho invitan a que contradigan entre sí — y en la práctica lo hacen, la primera vez que un resolver falla después de escribir parcialmente.
5. Manejo de Errores
5.1 Categorías de Errores
Los códigos de error son parte del contrato de la API — los clientes ramifican sobre ellos, así que deben ser estables y estar documentados como los nombres de campos. Renombrar un código de error es el mismo cambio incompatible que renombrar un campo, y los equipos lo olvidan más a menudo de lo que esperarías.
Categoría | Código | Equivalente HTTP
──────────────────┼───────────────────┼──────────────────
Validación | INVALID_INPUT | 400
Autenticación | UNAUTHENTICATED | 401
Autorización | FORBIDDEN | 403
No encontrado | NOT_FOUND | 404
Conflicto | CONFLICT | 409
Límite de tasa | RATE_LIMITED | 429
Interno | INTERNAL_ERROR | 500
UNAUTHENTICATED y FORBIDDEN son deliberadamente distintos: el primero significa “no podemos identificarte” (el cliente puede reintentar con credenciales), el segundo significa “te conocemos y la respuesta es no” (reintentar no ayuda).
5.2 Reglas de Errores
- Nunca expongas stack traces ni rutas internas — ni siquiera a administradores. Los atacantes también leen los mensajes de error.
- Los errores de campo incluyen la ruta
field - Los mensajes de error son para el usuario, no para el desarrollador
- Los códigos de error son estables y están documentados
- Los errores de rate limit incluyen
retryAfteren la extensión de error - Los errores internos se registran con IDs de correlación
- Ningún mensaje de error contiene datos personales ni secretos
// Formato de errores
function formatError(error) {
return {
message: error.message,
extensions: {
code: error.extensions?.code || 'INTERNAL_ERROR',
field: error.extensions?.field,
retryAfter: error.extensions?.retryAfter,
correlationId: context.correlationId,
},
};
}
El objeto extensions es donde vive el detalle legible por máquina — mantén message legible por humanos y pon en extensions todo aquello sobre lo que un cliente pueda ramificar (code, field, retryAfter). Los IDs de correlación son lo que permite a un usuario reportar “falló” y a tu equipo encontrar la línea exacta del log sin cazar timestamps. La primera vez que un usuario pegó un ID de correlación en un ticket de soporte y encontramos el resolver que fallaba en segundos, esta regla se pagó sola.
6. Autenticación y Autorización
6.1 Autenticación
La autenticación ocurre en la capa de transporte, antes de que GraphQL vea la petición. La factoría de contexto verifica el token una vez y entrega a cada resolver un objeto user — los resolvers no deberían parsear headers por su cuenta.
// Autenticación vía JWT en el header Authorization
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return { user: null };
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
return { user, loaders: createLoaders() };
} catch {
return { user: null };
}
},
});
Un token fallido devuelve user: null, no un error — la petición continúa y las reglas de autorización deciden qué puede ver un usuario anónimo. Esto mantiene los campos públicos funcionando sin una ruta de código especial de “no autenticado”. También he visto a un equipo depurar durante una tarde problemas de token de “en mi máquina funciona” porque dos resolvers parseaban el header de forma ligeramente distinta — la factoría de contexto existe para que ese bug no pueda repetirse.
6.2 Reglas de Autorización
- Cada resolver de campo comprueba la autorización
- Usa la directiva
@autho middleware para auth a nivel de campo - Los campos públicos se marcan explícitamente como públicos
- Los campos solo de admin comprueban el rol antes de resolver
- Ningún campo devuelve datos que el usuario no está autorizado a ver
- Los fallos de autorización devuelven
FORBIDDEN, no resultados vacíos
La auth a nivel de campo importa más en GraphQL que en REST porque una sola query puede tocar una docena de recursos. Un usuario que puede leer User.orders no debería leer automáticamente User.email — el schema hace esa distinción explícita con directivas en lugar de enterrarla en la lógica del resolver. Para una implementación funcional de auth basada en directivas, mira la receta de la directiva @auth.
directive @auth(requires: Role!) on FIELD_DEFINITION
enum Role {
ADMIN
MANAGER
USER
PUBLIC
}
type User {
id: ID!
email: String! @auth(requires: ADMIN)
orders: [Order!]! @auth(requires: USER)
auditLog: [AuditEntry!]! @auth(requires: ADMIN)
}
Devolver FORBIDDEN en lugar de resultados vacíos es deliberado: una lista de orders vacía y una lista de orders prohibida se ven idénticas para el cliente, y devolver [] en silencio enseña a los usuarios a desconfiar de los datos. Los errores explícitos hacen visibles los bugs de autorización en lugar de esconderlos como datos faltantes.
7. Rate Limiting y Control de Costes
7.1 Rate Limiting
Los límites por número de peticiones son necesarios pero no suficientes para GraphQL — una sola petición puede ser arbitrariamente cara. Necesitas tanto un límite de peticiones como un modelo de coste (sección 7.2).
Lo aprendí de la forma vergonzosa: nuestro primer límite de “100 peticiones/minuto” no sirvió de nada cuando un bot de scraping descubrió que podía anidar users { posts { comments { ... } } } y llevarse toda la base de datos en cincuenta peticiones. Contar peticiones es contar sobres; el análisis de coste es pesar lo que llevan dentro.
// Rate limiting por usuario
import { RateLimiterMemory } from 'rate-limiter-flexible';
const rateLimiter = new RateLimiterMemory({
points: 100, // 100 peticiones
duration: 60, // cada 60 segundos
});
// En el middleware de contexto
async function checkRateLimit(user) {
try {
await rateLimiter.consume(user.id);
} catch {
throw new GraphQLError('Rate limit exceeded', {
extensions: { code: 'RATE_LIMITED', retryAfter: 60 },
});
}
}
7.2 Reglas de Control de Costes
- Profundidad máxima de query: 10
- Complejidad máxima de query: 1000
- Máximo de alias por campo: 5
- Límite de tasa: 100 peticiones por minuto por usuario
- Límite de mutaciones: 30 por minuto por usuario
- Límite de suscripciones: 10 concurrentes por usuario
- Límite de subida de archivos: 10MB
- Límite de tamaño de respuesta: 5MB
La puntuación de complejidad asigna un coste a cada campo (las listas cuestan más, a menudo hijos × costeDelCampo) y rechaza las queries que superan el presupuesto antes de ejecutarlas. Es lo que impide que users { orders { items { product { reviews { author { ... } } } } } } se convierta en un volcado gratis de la base de datos — una query así sigue siendo una petición HTTP, pero puede ramificarse en miles de llamadas a resolvers.
El límite de alias cierra un bypass concreto: a: users { ... } b: users { ... } c: users { ... } ejecuta el campo caro tres veces en una petición mientras parece una sola query. Cinco alias por campo cubre el uso legítimo (comparar dos vistas filtradas) sin abrir la puerta a la multiplicación.
Ajusta estos números a tu tráfico. Una API pública para desarrolladores externos necesita límites más estrictos que una API interna detrás de tu propio frontend, y los campos caros (búsqueda, agregaciones) merecen su propio peso de coste mayor. Empieza estricto y afloja con evidencia: es mucho más fácil subir un límite después de un ticket enojado que bajarlo después de un incidente a las 3 de la mañana.
8. Versionado y Evolución
8.1 Reglas de Versionado
La respuesta de GraphQL al versionado es que no se versiona — se evoluciona. Un endpoint /v2/graphql significa mantener dos schemas para siempre; un schema aditivo significa que los clientes antiguos siguen funcionando mientras los nuevos usan campos nuevos.
- Nada de versionado por URL (no
/v2/graphql) - La evolución del schema es solo aditiva
- Los cambios incompatibles pasan por el proceso de deprecación (mínimo 6 meses)
- Los campos nuevos se pueden añadir sin versionado
- La eliminación de campos requiere cero uso durante 30 días
- Los cambios de tipo requieren crear un tipo nuevo + deprecación
La regla de “cero uso durante 30 días” solo funciona si mides el uso de los campos — por eso la analítica del schema (Apollo Studio, GraphQL Inspector o los logs de tu gateway) es un requisito previo para evolucionar con seguridad, no un extra opcional. Sin datos de uso, “¿alguien sigue consultando este campo?” no tiene respuesta.
8.2 Cambios Permitidos (Compatibles)
Añadir:
- Campos nuevos a tipos existentes
- Tipos nuevos
- Valores de enum nuevos (con documentación)
- Argumentos opcionales nuevos
- Interfaces nuevas
Cambiar:
- Descripción de campo
- Argumentos de directiva (aditivo)
Una salvedad sobre los valores de enum: añadir un valor es técnicamente compatible, pero un cliente que ramifica exhaustivamente sobre OrderStatus encontrará un caso sin manejar cuando aparezca BACKORDERED. Documenta el crecimiento esperado del enum en su descripción para que quienes desarrollan clientes escriban una rama por defecto.
8.3 Cambios Prohibidos (Incompatibles)
Eliminar:
- Campos
- Tipos
- Valores de enum
- Argumentos
Cambiar:
- Tipo de campo (String → Int)
- De non-null a nullable (aceptable pero desaconsejado)
- De nullable a non-null (incompatible)
- Valores por defecto de argumentos
- Tipos de argumentos
De nullable a non-null es el cambio sutil: cambia lo que el servidor promete, y cualquier cliente que manejaba null ahora tiene código muerto, pero peor — las queries almacenadas que dependían de que el campo fuera nullable pueden recibir errores donde antes recibían datos. Haz los campos non-null al crearlos o déjalos nullable para siempre.
Para el proceso completo de eliminación — período de deprecación, medición de uso y notificación a clientes — usa la plantilla de política de deprecación.
9. Reglas de Federación
9.1 Diseño de Entidades
La federación divide un schema entre varios servicios detrás de un gateway. Vale el coste operativo cuando los límites de equipo, los ciclos de despliegue o las necesidades de escalado divergen — y es exceso cuando no lo hacen.
- Cada subgraph es dueño de sus tipos de dominio
- Los tipos entidad usan
@key(fields: "id")para referencias entre subgraphs __resolveReferenceimplementado para todas las entidades- Ningún subgraph extiende más de 3 tipos de otros subgraphs
- Escalares y enums compartidos definidos en un paquete compartido
El límite de 3 extensiones mantiene honestos los límites de los subgraphs. Un subgraph que extiende cinco tipos de otros equipos no es un subgraph — es el viejo monolito con saltos de red extra y depuración más difícil. Cada vez que he visto ese límite roto, la verdadera historia eran dos equipos compartiendo una base de datos y llamándolo federación.
# Subgraph de productos
type Product @key(fields: "id") {
id: ID!
name: String!
price: Float!
categoryId: ID!
}
# Subgraph de reseñas — extiende Product
type Product @key(fields: "id") @extends {
id: ID! @external
reviews: [Review!]!
averageRating: Float!
}
Para los detalles operativos — configuración del gateway, comprobaciones de composición y migración de un monolito — mira la Guía Completa de GraphQL Federation y el patrón de entidad federada.
9.2 Checklist de Federación
Ejecuta este checklist antes de habilitar un subgraph nuevo en el gateway, y vuelve a ejecutarlo en cada cambio de composición del schema. Firman tanto el dueño del schema como el equipo de plataforma.
- Las claves de entidad son únicas globalmente
- No hay dependencias circulares entre subgraphs
- Los escalares personalizados se comparten vía paquete npm
- El schema compuesto se valida con
rover - La salud de cada subgraph se monitoriza por separado
- El gateway puede enrutar queries cuando un subgraph está caído
Cómo Adoptar Estos Lineamientos
Un documento de diseño que nadie aplica se convierte en arqueología. El camino de adopción que funciona:
- Publícalo como documento vivo. Estas reglas viven en el repo junto al schema, versionadas con él — no en una wiki que se desactualiza. Los cambios pasan por la misma revisión de PR que el código.
- Automatiza lo automatizable. Las convenciones de nombres, el uso de campos deprecados y los cambios incompatibles son mecánicos: aplícalos con
eslint-plugin-graphql, linting de schema en CI y comprobaciones de composición (rover subgraph check, GraphQL Inspector) en cada pull request. Las personas revisan decisiones de criterio; los linters capturan el resto. - Revisa con un checklist. Cada PR de schema pasa por el checklist de revisión de schemas GraphQL — convierte estas reglas en un aprobado/reprobado por cambio en lugar de un documento que la gente recuerda vagamente.
- Maneja las violaciones existentes explícitamente. Las APIs existentes que rompen una regla reciben una excepción registrada con fecha límite de corrección, no tolerancia silenciosa. De lo contrario el número de excepciones crece hasta que el lineamiento no significa nada.
- Incorpora equipos nuevos con la plantilla. La plantilla de onboarding de federación cubre la puesta al día específica de subgraphs para equipos que se unen a un gateway existente.
- Revisa los números cada trimestre. Los límites que tenían sentido con 10K queries diarias se pudren en silencio con 1M. Programa una revisión recurrente en el calendario — la versión del lineamiento que salió en enero no debería seguir siendo la que vigila el tráfico en diciembre.
See Also
Preguntas frecuentes
¿Debería usar suscripciones o webhooks para actualizaciones en tiempo real?
Usa suscripciones para funciones en tiempo real orientadas al usuario (chat, notificaciones, dashboards en vivo). Usa webhooks para integraciones de servidor a servidor. Las suscripciones mantienen una conexión persistente y escalan de forma distinta a las queries y mutaciones. Si manejas más de 1000 suscripciones concurrentes por servidor, considera un endpoint de suscripciones dedicado o un sistema pub/sub externo (Redis, NATS). Los webhooks son más fiables para integraciones porque reintentan en caso de fallo.
¿Cómo manejo la subida de archivos en GraphQL?
Usa el paquete graphql-upload con la especificación de petición multipart. Define un escalar Upload personalizado. Aplica límites de tamaño (10MB por defecto). Valida los tipos de archivo en el servidor. Guarda los archivos en almacenamiento de objetos (S3, GCS) y devuelve la URL en el payload de la mutación. No codifiques archivos en base64 en las mutaciones — infla la petición y rompe los proxies.
¿Cuál es el tamaño de página correcto para las queries de lista?
20 elementos por página por defecto. Máximo 100. Para queries caras (joins, agregaciones), reduce el máximo a 50. Expón el valor por defecto y el máximo en la documentación del schema. Deja que los clientes pidan páginas más pequeñas pero no más grandes. Para paneles de administración que necesitan datos en lote, ofrece un endpoint de exportación separado en lugar de permitir páginas grandes.
¿Cómo pruebo los resolvers de GraphQL?
Prueba los resolvers de forma aislada con mocks de la capa de datos. Prueba el argumento padre, los args, el contexto y la info. Haz pruebas de integración del schema completo con una base de datos de pruebas. Cubre los casos límite: retornos null, códigos de error, fallos de autorización, rate limiting. Usa snapshot testing para respuestas de queries complejas. Prueba las suscripciones conectando un cliente de pruebas y verificando la entrega de eventos.
¿Cuándo debería dividir un schema monolítico en federación?
Divide cuando los equipos necesiten ciclos de despliegue independientes, cuando el schema supere los 200 tipos, cuando distintos dominios tengan necesidades de escalado distintas, o cuando los límites de equipo coincidan con los límites de dominio. No dividas prematuramente — la federación añade complejidad operativa (gateway, composición, depuración entre subgraphs). Empieza con un monolito modular y divide cuando el dolor del acoplamiento supere el dolor de la federación.
Recursos Relacionados
Checklist de Revision de Schema GraphQL
Checklist para revisar schemas GraphQL: naming conventions, type design, pagination, error handling, security, performance, deprecation y federation readiness con ejemplos de codigo y validation rules.
DocPlantilla de Política de Deprecación de GraphQL
Plantilla de política para deprecar campos, tipos y valores enum de GraphQL sin romper clientes, con cronogramas, seguimiento de uso y ejemplos de migración.
GuideDiseño de Esquemas GraphQL
Disenar esquemas GraphQL para evolucion, rendimiento y mantenibilidad. Cubre diseno de tipos, conexiones, mutaciones, manejo de errores, deprecation y workflows schema-first vs code-first.
GuideGuía Completa de GraphQL Federation
Construye APIs GraphQL unificadas con Apollo Federation. Cubre subgraphs, supergraph, resolución de entidades y despliegue de gateway.
DocPlantilla de Onboarding a GraphQL Federation
Plantilla para onboardear un servicio a un federated GraphQL graph: subgraph setup, entity definitions, resolver configuration, gateway integration, testing, deployment y monitoring con ejemplos de codigo.