Programming·Updated May 10, 2026·16 min read·👁 10.1K views

GraphQL API AI Prompts: Build Production APIs Faster in 2026

John Allick· AI Researcher
📖 2,719 words
Quick Summary

Complete AI prompt library for GraphQL developers. Covers schema design, resolvers, authentication, DataLoader for N+1 prevention, subscriptions, Federation for microservices, persisted queries, testing, and deployment with Apollo Server or Yoga.

#GraphQL#Apollo#Node.js#TypeScript#Federation#AI Coding Prompts

GraphQL in 2026: Flexible APIs at Scale

GraphQL enables clients to request exactly the data they need, making it ideal for complex frontend applications, mobile clients with bandwidth constraints, and microservice federation. The teams shipping the most reliable GraphQL APIs use code-first TypeScript schemas, DataLoaders on every relationship, and shield-based authorization — patterns that AI generates perfectly when prompted correctly.

AI + GraphQL: The N+1 Default Problem

Every AI model generates N+1 GraphQL resolvers by default — without exception. This is the single biggest gap between AI-generated GraphQL and production GraphQL. A list resolver that fetches 50 projects and resolves each project's tasks fires one query for the projects, then 50 individual queries for tasks. In a test database with three projects it's invisible. With real traffic it saturates your database connection pool within minutes of deploy.

The fix must appear in every GraphQL prompt: "Add DataLoaders for all relationship resolvers. No direct ORM or database calls inside resolvers that traverse foreign keys — every relationship must go through DataLoader.load() or DataLoader.loadMany(). Create DataLoader instances per request in the context factory, not as global singletons." Claude handles this most reliably but still needs the explicit instruction. A second recurring gap: depth limiting and query complexity analysis. Without them, a malicious client can craft a deeply nested query that brings down your server. Add "include depth limit middleware (max depth: 7) and query complexity scoring" to every prompt building a public GraphQL API.

For the full model-by-model breakdown covering GraphQL alongside Vue and Django, see the Frontend & API Layer AI Prompts guide.

1. Code-First GraphQL Schema with Pothos

⌥ PROMPT
You are a GraphQL expert using Pothos (formerly GiraphQL) with TypeScript.

Build a type-safe GraphQL schema for a project management API:

Types to define:
- User, Organization, Project, Task, Comment, PageInfo, Connection types
- Task: id, title, description, status (enum), priority (enum), dueDate, assignedTo (User), project (Project), comments (CommentConnection), createdAt, updatedAt

Pothos setup:
- builder = new SchemaBuilder with: context type (user, organizationId, dataLoaders), plugins: [SimpleObjectsPlugin, RelayPlugin, ValidationPlugin, ScopeAuthPlugin]
- Relay plugin: automatic Connection and Edge types, cursor-based pagination on all list fields
- Enum types: TaskStatus (TODO, IN_PROGRESS, DONE, CANCELLED), Priority (LOW, MEDIUM, HIGH, CRITICAL)

Queries:
- task(id: ID!): Task — @auth(requires: AUTHENTICATED)
- tasks(filter: TaskFilterInput, first: Int, after: String): TaskConnection — paginated
- myTasks: [Task!]! — current user's assigned tasks

Mutations:
- createTask(input: CreateTaskInput!): CreateTaskPayload — returns task or errors array
- updateTask(id: ID!, input: UpdateTaskInput!): UpdateTaskPayload
- assignTask(taskId: ID!, userId: ID!): AssignTaskPayload

Output: schema builder setup, type definitions, query/mutation resolvers, and generated schema SDL.

2. DataLoader for N+1 Prevention

⌥ PROMPT
You are a GraphQL performance expert specialising in DataLoader patterns.

Build a complete DataLoader setup for a GraphQL API to eliminate N+1 queries:

DataLoaders required (one per relationship):
- userLoader: batch load users by ID → Map<string, User>
- projectLoader: batch load projects by ID
- tasksByProjectLoader: batch load tasks by project ID → Map<string, Task[]>
- commentsByTaskLoader: batch load comments by task ID
- assignedTasksLoader: batch load tasks assigned to each user ID

Each DataLoader:
- Created per GraphQL request (in context factory — not global singletons)
- Batch function: single DB query with WHERE id = ANY($1) (PostgreSQL) or IN clause
- Cache: DataLoader's built-in per-request cache (deduplicates within one request)
- Max batch size: 100 (prevent overly large IN clauses)
- Error handling: return Error instance for missing IDs (DataLoader per-item error)

Context factory:
function createContext(req): Context {
  return {
    user: extractUserFromJWT(req),
    db: dbConnection,
    loaders: {
      user: new DataLoader(batchLoadUsers),
      project: new DataLoader(batchLoadProjects),
      tasksByProject: new DataLoader(batchLoadTasksByProject),
      // ...
    }
  }
}

Show: all DataLoader batch functions with SQL, context factory, and resolver usage in Task.assignedTo and Project.tasks fields.

CRITICAL: explain why DataLoaders must be created per-request, not per-server.

Why it works: The "per-request, not per-server" requirement prevents the most catastrophic DataLoader mistake — a shared cache that leaks data between users. This single line prevents a serious security vulnerability.

3. Authentication & Authorization with Directives

⌥ PROMPT
You are a GraphQL security architect.

Implement field-level authentication and authorization for a GraphQL API:

Authentication (Apollo Server context):
- Extract JWT from Authorization header in contextFunction
- Verify RS256 signature, check expiry
- Attach to context: { user: { id, organizationId, role } | null }
- Never throw in context creation — let resolvers decide what's public

Authorization approach — Schema Directives:
- @auth(requires: Role) directive on type fields
- @ownerOnly — resolver checks resource.userId === context.user.id
- @rateLimit(max: Int, window: String) — per-user field rate limiting

Directive implementation:
- mapSchema + MapperKind to wrap resolver functions at schema build time
- Auth directive: if context.user is null or wrong role → throw AuthenticationError (Apollo error code)
- Tenant isolation: every resolver that touches DB must filter by context.user.organizationId

Error handling:
- Distinguish: AuthenticationError (unauthenticated) vs ForbiddenError (wrong role)
- Never expose internal resolver errors — mask with "Internal server error" + log full error with request ID
- Extensions: include error code in { extensions: { code: 'UNAUTHENTICATED' } }

Output: directive definitions, mapSchema implementation, and 5 examples on type fields.

4. GraphQL Subscriptions

⌥ PROMPT
You are a GraphQL real-time expert.

Implement GraphQL subscriptions for real-time task updates:

Subscriptions:
- taskUpdated(projectId: ID!): Task — emits when any task in the project is updated
- taskAssigned(userId: ID!): Task — emits when a task is assigned to the user
- projectActivity(projectId: ID!): ActivityEvent — all project events stream

PubSub setup:
- graphql-redis-subscriptions with Redis for horizontal scaling (not in-memory PubSub)
- Channel naming: task:updated:{projectId}, task:assigned:{userId}
- Authorization in subscription resolver: verify user can access the projectId before subscribing

Publishing from mutations:
- After createTask mutation: pubsub.publish('task:updated:{projectId}', { taskUpdated: task })
- After assignTask mutation: pubsub.publish('task:assigned:{userId}', { taskAssigned: task })

WebSocket setup (Apollo Server + graphql-ws):
- wsServer: separate WebSocket server on same HTTP server
- Auth: extract JWT from connection params (not headers — WebSocket handshake headers are limited)
- Cleanup: on disconnect, close any open DB queries or subscriptions

Output: subscription type definitions, resolver with asyncIterator, Redis PubSub config, and wsServer setup.

5. Apollo Federation for Microservices

⌥ PROMPT
You are an Apollo Federation expert.

Design a federated GraphQL schema across 3 microservices:

Subgraph 1 — users-service (owns User type):
- User @key(fields: "id"): id, email, name, role, createdAt
- Query: me, user(id: ID!), users(filter: UserFilter): [User!]!

Subgraph 2 — projects-service (owns Project, Task types):
- Project @key(fields: "id"): id, name, description, owner: User (reference)
- Task @key(fields: "id"): id, title, status, assignedTo: User (reference), project: Project
- Extends User @key(fields: "id") to add: assignedTasks: [Task!]! (defined in this subgraph)

Subgraph 3 — notifications-service (owns Notification type):
- Notification @key(fields: "id"): id, type, message, recipient: User (reference), read

Apollo Router (supergraph) config:
- Compose subgraph schemas with rover subgraph introspect
- JWT validation at router level (propagate as x-user-id, x-organization-id headers to subgraphs)
- Query plan caching
- Response caching with @cacheControl directives

Entity resolution:
- Each subgraph implements __resolveReference for its @key types
- Show how User is resolved across services without a shared DB

Output: all 3 subgraph schemas with @key, reference resolvers, and supergraph YAML config.

6. GraphQL Testing

⌥ PROMPT
You are a GraphQL testing expert.

Write comprehensive tests for a GraphQL task API using Jest:

Test setup:
- ApolloServer in test mode: new ApolloServer({ schema }) + startStandaloneServer or executeOperation
- In-memory test context factory: mock DataLoaders, seed database via Prisma test client
- JWT factory: createTestToken(userId, role, organizationId)

Test operations (use gql tagged template literals):

Query tests:
1. task query: authenticated, returns full task with nested assignee (DataLoader called once — assert DB call count)
2. task query: returns null for other org's task (tenant isolation)
3. task query: unauthenticated → extensions.code === 'UNAUTHENTICATED'

Mutation tests:
4. createTask: valid input → task created in DB, subscription event published
5. createTask: validation failure → errors array with field-level messages (not a single error)
6. createTask: assignee from different org → FORBIDDEN error code

DataLoader test:
7. Fetch 5 tasks in one query → assert assignee DataLoader batch function called exactly once (spy on DB)

Output: Jest setup, reusable test helpers, and all 7 test cases with gql operations.

8. GraphQL Subscriptions for Real-Time Updates

⌥ PROMPT
You are a GraphQL real-time systems engineer.

Add GraphQL subscriptions to an Apollo Server 4 + TypeScript API:

Transport: graphql-ws (WebSocket) — not the deprecated subscriptions-transport-ws
Schema additions:
  type Subscription {
    taskUpdated(projectId: ID!): TaskUpdatedPayload!
    commentAdded(taskId: ID!): Comment!
    userPresence(projectId: ID!): UserPresencePayload!
  }

PubSub: Redis-based (graphql-redis-subscriptions) for horizontal scaling — not in-memory PubSub
Authentication on subscription: verify JWT in ConnectionParams (passed on WebSocket connect), attach user to context
Subscription resolvers:
  - taskUpdated: filter pubsub events by projectId, verify user has access to that project
  - commentAdded: DataLoader batch for each emitted comment (don't fetch in subscription resolver)
  - userPresence: track connected users per project in Redis, broadcast join/leave events

Publishing: from REST mutation resolver AND from external service via Redis pub/sub
Apollo Studio support: configure subscriptions endpoint separately from HTTP endpoint

Output: subscription type definitions, resolver implementations, Redis PubSub setup, WebSocket server configuration, and client-side Apollo Client subscription hook in React.

9. GraphQL Federation with Apollo Router

⌥ PROMPT
You are a GraphQL federation architect.

Design a federated GraphQL architecture for a microservices system:

Subgraphs:
- users-service: User type (owner)
- tasks-service: Task type, extends User with tasks field
- notifications-service: Notification type, extends User with notifications field

Each subgraph uses Pothos with the Federation plugin:
- users-service: @key(fields: "id") on User, resolveReference implementation
- tasks-service: @extends User, @external id field, @requires for user.organizationId
- notifications-service: @extends User

Apollo Router configuration:
- Supergraph schema: compose with rover CLI in CI
- Query planning: router logs query plans in development
- Authorization: JWT verification in router (not per-subgraph) using Rhai script
- Rate limiting: per-operation rate limiting in router config (100/min default, 10/min for expensive operations)
- Persisted queries: register known query hashes, reject unknown queries in production

Local development: rover dev for hot-reloading subgraph composition

Output: Pothos federation setup for each subgraph, Apollo Router config YAML, rover composition command, and CI pipeline step for supergraph validation.

End-to-End Workflow: New GraphQL Feature

Adding a comments feature to a GraphQL API:

  1. Schema (Prompt 1 variant): "Add Comment type to the Pothos schema: id, content, author (User type — use DataLoader), task (Task type — use DataLoader), createdAt, isEdited. Add Query.taskComments(taskId: ID!, first: Int, after: String): CommentConnection. Add Mutation.createComment, updateComment, deleteComment."
  2. DataLoaders: "Create a CommentDataLoader using dataloader library: batches comment IDs, queries Prisma in one call, returns in same order as input IDs. Create a CommentsForTaskDataLoader: batch by taskId array, return Map<taskId, Comment[]>."
  3. Resolvers: "Write Comment resolvers: author → userDataLoader.load(comment.authorId), task → taskDataLoader.load(comment.taskId). Subscription: commentAdded(taskId) with Redis PubSub, filter by taskId, verify access."
  4. Authorization (Prompt 4 variant): "Add graphql-shield rules for Comment: isAuthenticated on all queries, isMember (user belongs to comment's task's project) for reads, isCommentAuthor for update/delete."
  5. Tests (Prompt 6 variant): "Write Jest + Apollo Client integration tests for the comments schema: createComment mutation, commentAdded subscription fires on create, deleteComment returns error for non-owner."

Where AI Goes Wrong in GraphQL

  • N+1 resolvers without DataLoaders. The #1 GraphQL performance problem, and AI generates it by default. Every field resolver that loads a relationship must use a DataLoader. Without DataLoaders, a list of 100 tasks with author fields fires 100 separate user queries. Always specify "DataLoader for every relationship — never direct ORM calls in resolvers."
  • Missing query complexity limits. AI-generated Apollo Server configs rarely include depth limiting or complexity analysis. A malicious client can craft a deeply nested query that causes exponential DB load. Add graphql-depth-limit and graphql-validation-complexity to every production schema.
  • Authorization in the wrong layer. AI puts authorization logic inside resolvers — complex and inconsistent. Use graphql-shield to define authorization rules once per type/field, applied as middleware to the schema. Much cleaner and testable.
  • SDL-first schema when code-first is specified. If you ask for Pothos or type-graphql, verify the output isn't SDL with resolvers attached as strings. Code-first and SDL-first are fundamentally different patterns.
  • subscriptions-transport-ws instead of graphql-ws. The old subscriptions-transport-ws library is unmaintained. graphql-ws is the current standard. AI trained before 2023 almost always generates the deprecated library.

7. Good vs Bad GraphQL Prompts

Task❌ Bad Prompt✅ Good Prompt
Schema design"Build a GraphQL API for tasks""Build a Pothos code-first TypeScript GraphQL schema for Task: Relay connection pagination on tasks query, TaskStatus enum, assignedTo resolver using DataLoader (batch by ID, created per-request in context), @auth directive on all mutations. Return mutation payload type with task and errors fields."
N+1 prevention"Load task assignees""Build userDataLoader for GraphQL context: batch function fetches all user IDs in one SELECT WHERE id = ANY($1) query, max batch size 100, created per-request (not global). Show how Task.assignedTo resolver calls context.loaders.user.load(task.assignedToId)."
Authorization"Add auth to my GraphQL API""Implement @auth(requires: ADMIN) schema directive with mapSchema: wrap resolver, check context.user.role, throw ForbiddenError with extensions.code='FORBIDDEN' if denied. Tenant isolation: every DB query filters by context.user.organizationId. Auth in context, not in resolvers."

Before You Prompt: GraphQL Context Setup

GraphQL's N+1 problem is the most expensive mistake in AI-generated code — and it's invisible until the API is under load. A GraphQL server without DataLoaders on every relationship resolves a list of 50 users by firing 51 database queries. This block mandates DataLoaders and the other production-critical patterns:

⌥ PROMPT
Context for all GraphQL prompts in this session:
- Language: TypeScript 5.4, strict mode
- Schema: Code-first with Pothos (NOT SDL-first in TypeScript projects)
- Server: GraphQL Yoga 5 OR Apollo Server 4 (specify which)
- DataLoader: MANDATORY on every resolver that returns a related entity
  Every field resolving a relationship must use a DataLoader — no exceptions
  Never write a resolver that calls db.findById() or db.findMany() directly without DataLoader
- Authorization: graphql-shield for field and type-level rules
  Never put auth logic inside individual resolver functions
- Security: depth limiting (max 10 levels), query complexity analysis (max 1000 points)
- Testing: graphql-request for full operation tests (NOT resolver unit tests)

The DataLoader requirement deserves extra emphasis beyond the context block: in every prompt that involves a resolver returning related data, add "this resolver must use a DataLoader — show the DataLoader definition alongside the resolver." DataLoaders are the #1 thing AI forgets because they're architecturally separated from the resolver but required for it to perform correctly.

3 Common Mistakes When Prompting AI for GraphQL

Mistake 1: Resolvers without DataLoaders

The default GraphQL resolver pattern — resolve: (parent) => db.findUser(parent.userId) — fires one database query per object in the parent list. For 50 posts, that's 50 separate user lookups. A DataLoader batches all 50 lookups into a single SELECT * FROM users WHERE id IN (...). AI generates the N+1 pattern by default because it's simpler. Specify: "every resolver that accesses a related entity must use a DataLoader passed through context — show the DataLoader definition and how it's initialized per request." This eliminates the #1 GraphQL performance bug.

Mistake 2: Authorization logic inside individual resolvers

Asking to "add authorization to this resolver" produces inline permission checks: if (!context.user.isAdmin) throw new ForbiddenError(). When you have 30 resolvers and 5 permission rules, this approach leads to inconsistent enforcement — some resolvers get the check, some don't. Specify: "use graphql-shield rules that apply at the type and field level — no inline auth in resolver functions." Shield applies authorization as middleware, so a forgotten check on one resolver doesn't create a security gap.

Mistake 3: No query depth or complexity limits

GraphQL allows arbitrarily nested queries. Without limits, a malicious client can send a deeply recursive query that exponentially multiplies database lookups: { users { posts { comments { author { posts { ... } } } } } }. AI doesn't add depth or complexity limits because most examples in training data don't include them. Specify: "add depth limiting (max 10 levels via graphql-depth-limit) and query complexity analysis (max 1000 points via graphql-query-complexity). Reject queries that exceed either limit with a 400 response."

Further Reading

Resources for AI-assisted GraphQL development:

Generate a custom GraphQL prompt → Try PromptPrepare free

Help & Answers

Frequently Asked Questions

John AllickAI Researcher· Updated May 10, 2026

John Allick is an AI researcher specializing in prompt engineering and large language model evaluation. He benchmarks models across ChatGPT, Claude, Gemini, Grok, and DeepSeek, focusing on practical techniques that produce reliable, production-ready outputs. Every guide on PromptPrepare is tested live on current model versions before publication.

✓ Expert-tested on live models✓ Updated May 10, 2026✓ Model-verified examples

Found this helpful?

Save it to your library or share with your team.

Keep Reading

Related Guides

Apply this guide instantly

Free AI prompt generator