Skip to main content
RapidDev - Software Development Agency
cursor-tutorial

How to generate typed APIs with Cursor

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.

What you'll learn

  • How to reference GraphQL codegen types in Cursor prompts
  • How to set up .cursorrules for typed resolver generation
  • How to generate resolvers that match your schema exactly
  • How to add context typing and authentication to resolvers
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner6 min read10-15 minCursor Free+, TypeScript with GraphQL (Apollo, Mercurius, or yoga)March 2026RapidDev Engineering Team
TL;DR

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

1

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.

codegen.yml
1# codegen.yml
2overwrite: true
3schema: "src/schema/**/*.graphql"
4generates:
5 src/generated/graphql.ts:
6 plugins:
7 - typescript
8 - typescript-resolvers
9 config:
10 contextType: "../context#GraphQLContext"
11 mapperTypeSuffix: Model
12 useIndexSignature: true

Pro 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.

2

Add GraphQL rules to .cursor/rules

Create rules that tell Cursor to always import from the generated types file and follow your resolver conventions.

.cursor/rules/graphql.mdc
1---
2description: GraphQL resolver conventions
3globs: "src/resolvers/**/*.ts"
4alwaysApply: true
5---
6
7## GraphQL Resolver Rules
8- ALWAYS import resolver types from @/generated/graphql
9- ALWAYS type resolvers using generated Resolvers type or individual QueryResolvers, MutationResolvers
10- ALWAYS type the context parameter as GraphQLContext from @/context
11- Handle nullable fields: return null explicitly, not undefined
12- Use DataLoader for N+1 prevention in nested resolvers
13- Throw GraphQLError for user-facing errors with proper extensions
14- NEVER use 'any' type in resolver arguments or return values
15- Validate mutation inputs before processing

Expected result: Cursor follows typed GraphQL resolver conventions whenever generating resolver code.

3

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.

src/resolvers/user.queries.ts
1// Cursor Chat prompt (Cmd+L):
2// @src/generated/graphql.ts @src/schema/user.graphql
3// @src/context.ts
4// Generate the User query resolvers: user(id: ID!) and
5// users(page: Int, limit: Int). Use the generated
6// QueryResolvers type. Access the database through
7// context.db. Return types must match the schema exactly.
8
9import type { QueryResolvers } from '@/generated/graphql';
10import { GraphQLError } from 'graphql';
11
12export 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.

4

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.

src/resolvers/user.mutations.ts
1// Cursor Chat prompt (Cmd+L):
2// @src/generated/graphql.ts @src/context.ts
3// Generate the createUser and updateUser mutation resolvers.
4// Check that context.user exists (authenticated).
5// Validate email format. Use the generated MutationResolvers type.
6
7import type { MutationResolvers } from '@/generated/graphql';
8import { GraphQLError } from 'graphql';
9
10export 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.

5

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.

src/resolvers/user.fields.ts
1// Cursor Chat prompt (Cmd+L):
2// @src/generated/graphql.ts Generate the User field
3// resolver for 'posts' that loads the user's posts.
4// Use context.loaders.postsByUserId DataLoader to
5// prevent N+1 queries. Type it using UserResolvers.
6
7import type { UserResolvers } from '@/generated/graphql';
8
9export 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

src/resolvers/user.queries.ts
1import type { QueryResolvers } from '@/generated/graphql';
2import { GraphQLError } from 'graphql';
3
4export 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 }
11
12 const user = await context.db.user.findUnique({
13 where: { id },
14 });
15
16 if (!user) {
17 throw new GraphQLError(`User not found: ${id}`, {
18 extensions: { code: 'NOT_FOUND' },
19 });
20 }
21
22 return user;
23 },
24
25 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));
28
29 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 ]);
37
38 return {
39 items,
40 total,
41 page,
42 limit,
43 hasNext: page * limit < total,
44 };
45 },
46
47 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.

ChatGPT Prompt

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.

Cursor Prompt

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.'

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.