Cursor generates untyped GraphQL resolvers by default because it lacks context about your schema and codegen types. By referencing generated types with @file, adding GraphQL rules to .cursorrules, and using a schema-first workflow, you get resolvers with full type safety, proper error handling, and context typing that match your GraphQL schema exactly.
Generating typed GraphQL APIs with Cursor
GraphQL resolvers need precise typing to match the schema contract. Without codegen type references, Cursor generates resolvers with `any` types, incorrect return shapes, and missing nullable handling. This tutorial shows how to connect Cursor to your GraphQL type generation output so every resolver it creates is fully type-safe.
Prerequisites
- Cursor installed with a TypeScript + GraphQL project
- GraphQL Code Generator configured (graphql-codegen)
- A GraphQL schema defined (.graphql files)
- Generated TypeScript types from codegen
Step-by-step guide
Generate types from your schema
Generate types from your schema
Run GraphQL Code Generator to produce TypeScript types from your schema. These generated types are what Cursor needs to create properly typed resolvers. Cursor cannot read .graphql schema files directly for type inference.
1# codegen.yml2overwrite: true3schema: "src/schema/**/*.graphql"4generates:5 src/generated/graphql.ts:6 plugins:7 - typescript8 - typescript-resolvers9 config:10 contextType: "../context#GraphQLContext"11 mapperTypeSuffix: Model12 useIndexSignature: truePro tip: Configure contextType in codegen.yml so generated resolver types include your authenticated context shape. This gives Cursor full visibility into context.user and context.db.
Expected result: Generated TypeScript types at src/generated/graphql.ts with resolver type definitions.
Add GraphQL rules to .cursor/rules
Add GraphQL rules to .cursor/rules
Create rules that tell Cursor to always import from the generated types file and follow your resolver conventions.
1---2description: GraphQL resolver conventions3globs: "src/resolvers/**/*.ts"4alwaysApply: true5---67## GraphQL Resolver Rules8- ALWAYS import resolver types from @/generated/graphql9- ALWAYS type resolvers using generated Resolvers type or individual QueryResolvers, MutationResolvers10- ALWAYS type the context parameter as GraphQLContext from @/context11- Handle nullable fields: return null explicitly, not undefined12- Use DataLoader for N+1 prevention in nested resolvers13- Throw GraphQLError for user-facing errors with proper extensions14- NEVER use 'any' type in resolver arguments or return values15- Validate mutation inputs before processingExpected result: Cursor follows typed GraphQL resolver conventions whenever generating resolver code.
Generate a typed query resolver
Generate a typed query resolver
Ask Cursor to generate a resolver by referencing the generated types and your schema. The AI will use the exact types from codegen, ensuring the resolver matches the schema contract.
1// Cursor Chat prompt (Cmd+L):2// @src/generated/graphql.ts @src/schema/user.graphql3// @src/context.ts4// Generate the User query resolvers: user(id: ID!) and5// users(page: Int, limit: Int). Use the generated6// QueryResolvers type. Access the database through7// context.db. Return types must match the schema exactly.89import type { QueryResolvers } from '@/generated/graphql';10import { GraphQLError } from 'graphql';1112export const userQueries: QueryResolvers = {13 user: async (_parent, { id }, context) => {14 const user = await context.db.user.findUnique({ where: { id } });15 if (!user) {16 throw new GraphQLError('User not found', {17 extensions: { code: 'NOT_FOUND' },18 });19 }20 return user;21 },22 users: async (_parent, { page = 1, limit = 20 }, context) => {23 return context.db.user.findMany({24 skip: (page - 1) * limit,25 take: limit,26 });27 },28};Expected result: Typed query resolvers that match the schema with proper context access and error handling.
Generate a typed mutation resolver
Generate a typed mutation resolver
Mutations need input validation and proper error handling. Ask Cursor to generate mutation resolvers with authentication checks and input validation.
1// Cursor Chat prompt (Cmd+L):2// @src/generated/graphql.ts @src/context.ts3// Generate the createUser and updateUser mutation resolvers.4// Check that context.user exists (authenticated).5// Validate email format. Use the generated MutationResolvers type.67import type { MutationResolvers } from '@/generated/graphql';8import { GraphQLError } from 'graphql';910export const userMutations: MutationResolvers = {11 createUser: async (_parent, { input }, context) => {12 if (!context.user) {13 throw new GraphQLError('Authentication required', {14 extensions: { code: 'UNAUTHENTICATED' },15 });16 }17 if (!input.email.includes('@')) {18 throw new GraphQLError('Invalid email format', {19 extensions: { code: 'BAD_USER_INPUT' },20 });21 }22 return context.db.user.create({ data: input });23 },24};Expected result: Typed mutation resolvers with authentication, validation, and proper GraphQL error codes.
Generate nested field resolvers
Generate nested field resolvers
For relationships like User.posts, generate field resolvers that prevent N+1 queries. Reference the generated types and mention DataLoader if available.
1// Cursor Chat prompt (Cmd+L):2// @src/generated/graphql.ts Generate the User field3// resolver for 'posts' that loads the user's posts.4// Use context.loaders.postsByUserId DataLoader to5// prevent N+1 queries. Type it using UserResolvers.67import type { UserResolvers } from '@/generated/graphql';89export const userFieldResolvers: UserResolvers = {10 posts: async (parent, _args, context) => {11 return context.loaders.postsByUserId.load(parent.id);12 },13 fullName: (parent) => {14 return `${parent.firstName} ${parent.lastName}`;15 },16};Expected result: Field resolvers with DataLoader integration and computed fields, fully typed from codegen.
Complete working example
1import type { QueryResolvers } from '@/generated/graphql';2import { GraphQLError } from 'graphql';34export const userQueries: QueryResolvers = {5 user: async (_parent, { id }, context) => {6 if (!id) {7 throw new GraphQLError('User ID is required', {8 extensions: { code: 'BAD_USER_INPUT' },9 });10 }1112 const user = await context.db.user.findUnique({13 where: { id },14 });1516 if (!user) {17 throw new GraphQLError(`User not found: ${id}`, {18 extensions: { code: 'NOT_FOUND' },19 });20 }2122 return user;23 },2425 users: async (_parent, args, context) => {26 const page = Math.max(1, args.page ?? 1);27 const limit = Math.min(100, Math.max(1, args.limit ?? 20));2829 const [items, total] = await Promise.all([30 context.db.user.findMany({31 skip: (page - 1) * limit,32 take: limit,33 orderBy: { createdAt: 'desc' },34 }),35 context.db.user.count(),36 ]);3738 return {39 items,40 total,41 page,42 limit,43 hasNext: page * limit < total,44 };45 },4647 me: async (_parent, _args, context) => {48 if (!context.user) {49 throw new GraphQLError('Not authenticated', {50 extensions: { code: 'UNAUTHENTICATED' },51 });52 }53 return context.db.user.findUnique({54 where: { id: context.user.id },55 });56 },57};Common mistakes when generating typed APIs with Cursor
Why it's a problem: Not referencing the generated types file in Cursor prompts
How to avoid: Always include @src/generated/graphql.ts in GraphQL resolver prompts.
Why it's a problem: Returning undefined instead of null for nullable fields
How to avoid: Add to .cursorrules: 'Return null explicitly for absent nullable fields, never undefined.'
Why it's a problem: Skipping DataLoader for nested relationship resolvers
How to avoid: Mention DataLoader in your prompt for any resolver that loads related entities.
Best practices
- Run graphql-codegen before generating resolvers so types are current
- Always reference @src/generated/graphql.ts in resolver generation prompts
- Configure contextType in codegen.yml for typed context parameters
- Use DataLoader for all nested relationship resolvers to prevent N+1 queries
- Throw GraphQLError with proper extension codes (UNAUTHENTICATED, NOT_FOUND, BAD_USER_INPUT)
- Validate mutation inputs at the resolver level before calling services
- Split resolvers into queries, mutations, and field resolvers for maintainability
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Create TypeScript GraphQL resolvers for a User type with queries (user by ID, users list with pagination) and mutations (createUser, updateUser). Use generated types from graphql-codegen with QueryResolvers and MutationResolvers types. Include authentication checks, input validation, and proper GraphQLError throwing with extension codes.
In Cursor Chat (Cmd+L): @src/generated/graphql.ts @src/context.ts @src/schema/user.graphql Generate all resolvers for the User type: queries (user, users, me), mutations (createUser, updateUser, deleteUser), and field resolvers (posts, fullName). Use generated types. Use context.db for database access. Use DataLoader for nested fields.
Frequently asked questions
Do I need graphql-codegen to generate typed resolvers?
Strongly recommended. Without codegen, Cursor generates approximate types that may drift from your actual schema. Codegen ensures exact type parity between schema and resolvers.
Can Cursor generate the GraphQL schema itself?
Yes. Ask Cursor to generate .graphql schema files, then run codegen to produce types. However, start with the schema and generate resolvers from it, not the reverse.
How do I handle file uploads in Cursor-generated resolvers?
Ask Cursor to use graphql-upload for file handling. Reference the Upload scalar in your schema and specify the upload processing logic in your prompt.
Will Cursor handle subscription resolvers?
Yes. Specify your pubsub implementation (e.g., graphql-subscriptions) and ask Cursor to generate subscribe resolvers with proper typing and topic filtering.
How do I generate resolvers for a federated GraphQL schema?
Add federation directives to your codegen config and reference the generated types. Tell Cursor about your federation setup: 'This is a federated subgraph. Use __resolveReference for entity types.'
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation