A

Advisor Graphql Architect

All-in-one agent covering agent, designing, evolving, graphql. Includes structured workflows, validation checks, and reusable patterns for api graphql.

AgentClipticsapi graphqlv1.0.0MIT
0 views0 copies

Advisor GraphQL Architect

An autonomous agent that designs performant GraphQL schemas — defining types, resolvers, query optimization, N+1 prevention with DataLoader, and federated gateway architectures for complex data graphs.

When to Use This Agent

Choose Advisor GraphQL Architect when:

  • You are building a new GraphQL API and need schema design guidance
  • Existing GraphQL queries are slow due to N+1 problems or over-fetching
  • You need to implement GraphQL federation across multiple services
  • You want subscription support for real-time data updates

Consider alternatives when:

  • Your API has simple CRUD with a single consumer (REST is simpler)
  • You need binary protocol efficiency for microservice communication (use gRPC)
  • You are adding GraphQL to a project that already has a mature REST API (evaluate carefully)

Quick Start

# .claude/agents/graphql-architect.yml name: advisor-graphql-architect description: Design performant GraphQL schemas and architectures agent_prompt: | You are a GraphQL Architect. When designing GraphQL systems: 1. Model the domain as a graph of interconnected types 2. Design queries for client use cases, not database tables 3. Implement DataLoader for batched, cached data loading 4. Add query complexity analysis to prevent abuse 5. Set up persisted queries for production security 6. Design mutations with input types and payload types Performance rules: - ALWAYS use DataLoader for any relationship resolver - Limit query depth to 7 levels max - Set query complexity limits (cost per field) - Use cursor pagination, never offset

Example invocation:

claude "Design a GraphQL schema for a project management app with users, projects, tasks, and comments"

Sample schema design:

type Query { viewer: User! project(id: ID!): Project projects(first: Int!, after: String, filter: ProjectFilter): ProjectConnection! } type User { id: ID! name: String! email: String! projects(first: Int!, after: String): ProjectConnection! assignedTasks(status: TaskStatus): [Task!]! } type Project { id: ID! name: String! owner: User! members: [User!]! tasks(first: Int!, after: String, status: TaskStatus): TaskConnection! stats: ProjectStats! } type Task { id: ID! title: String! status: TaskStatus! assignee: User project: Project! comments(first: Int!, after: String): CommentConnection! createdAt: DateTime! updatedAt: DateTime! }

Core Concepts

Schema Design Principles

PrincipleRuleExample
Graph thinkingModel relationships, not tablestask.assignee not task.assigneeId
Client-drivenDesign for UI componentsviewer.dashboard aggregation query
ConnectionsCursor-based paginationProjectConnection { edges, pageInfo }
Input typesStructured mutation inputsinput CreateTaskInput { title, assigneeId }
Payload typesRich mutation responsestype CreateTaskPayload { task, errors }

DataLoader Pattern (N+1 Prevention)

// Without DataLoader: N+1 queries // Query: { projects { owner { name } } } // → 1 query for projects + N queries for each owner // With DataLoader: 2 queries total import DataLoader from 'dataloader'; const userLoader = new DataLoader(async (userIds: string[]) => { const users = await db.users.findMany({ where: { id: { in: userIds } } }); // Return in same order as requested IDs return userIds.map(id => users.find(u => u.id === id) || null); }); const resolvers = { Project: { owner: (project) => userLoader.load(project.ownerId), }, Task: { assignee: (task) => task.assigneeId ? userLoader.load(task.assigneeId) : null, } };

Query Complexity Analysis

// Prevent expensive queries from consuming resources import { createComplexityLimitRule } from 'graphql-validation-complexity'; const complexityRule = createComplexityLimitRule(1000, { scalarCost: 1, objectCost: 2, listFactor: 10, // Multiply cost by expected list size introspectionListFactor: 2, // Custom field costs formatComplexity: ({ type, field, args }) => { if (field.name === 'search') return 50; // Search is expensive if (args.first > 100) return args.first; // Large pages cost more return undefined; // Use default } }); // Reject queries exceeding cost 1000 // Example costs: // { viewer { name } } = 3 // { projects(first: 20) { edges { node { tasks(first: 50) { ... } } } } } = 1000+

Configuration

OptionTypeDefaultDescription
maxQueryDepthnumber7Maximum nesting depth for queries
maxQueryComplexitynumber1000Maximum query cost score
enableDataLoaderbooleantrueAuto-generate DataLoader recommendations
paginationStylestring"relay"Pagination: relay-connections, simple-cursor
enableSubscriptionsbooleanfalseInclude subscription type design
federationbooleanfalseDesign for Apollo Federation

Best Practices

  1. Use DataLoader for every relationship resolver — Even if you think a resolver will only be called once, wrap it in DataLoader. As your schema grows and queries become more complex, resolvers that were called once will be called in lists. DataLoader batches and caches by default, so there is zero overhead for single calls.

  2. Design mutations with input/payload types — Every mutation should accept a single input argument and return a payload type that includes both the result and potential user-facing errors. This pattern scales well, supports field-level validation errors, and makes schema evolution easier.

  3. Implement query complexity limits before production — Without limits, a single malicious or careless query can load your entire database through deeply nested relationships. Set field costs, list multipliers, and a maximum total complexity. Reject queries exceeding the limit before execution.

  4. Use Relay-style connections for all paginated fields — The Connection { edges { node, cursor }, pageInfo { hasNextPage, endCursor } } pattern is verbose but proven. It supports cursor-based pagination, carries edge metadata, and works with every GraphQL client pagination library.

  5. Never expose internal IDs or database structure — Use opaque IDs (base64-encoded Type:ID), avoid field names that match database column names exactly (use createdAt not created_at), and never expose fields like _id, rowId, or sequence. The schema should model the domain, not the database.

Common Issues

N+1 queries despite using DataLoader — DataLoader batches within a single tick of the event loop. If your resolvers use await between loading related data, the batching window may close before all keys are collected. Ensure all DataLoader .load() calls happen synchronously within the resolver, and use Promise.all() for multiple loads.

Schema becomes too large to navigate — As the schema grows to hundreds of types, developers struggle to find the right queries. Organize the schema into logical domains (User, Project, Billing), use schema stitching or federation to split the implementation across files, and maintain an interactive GraphQL explorer (like GraphiQL or Apollo Studio) as the primary documentation.

Subscriptions cause memory leaks under load — Each subscription holds an open WebSocket connection and a database change listener. Under high load, these listeners accumulate and consume memory. Implement connection limits per user, automatic subscription cleanup on disconnect, and heartbeat mechanisms to detect and close stale connections.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates