PJPalJS

Prisma Resolver Types Generator

Generate comprehensive TypeScript type definitions for GraphQL resolvers based on Prisma schema. This generator creates type-safe resolver function signatures and complete mapping from GraphQL schema types to TypeScript types.

View Template on GitHub

Overview

The Prisma Resolver Types Generator is essential for creating type-safe GraphQL resolvers. It analyzes your Prisma schema and generates:

  • Type-safe resolver function signatures with proper parent, args, context, and return types
  • Complete input/output type definitions for all GraphQL operations
  • Enum and scalar type mappings from Prisma to TypeScript
  • Context typing for resolver functions
  • Aggregate and special operation types with proper generic handling

Configuration Options

typescript
interface ResolverTypesGeneratorOptions {
  output?: string; // Output file path (default: "resolversTypes.ts")
  excludeFields?: string[]; // Fields to exclude globally
  excludeModels?: Array<{
    name: string;
    queries?: boolean;
    mutations?: boolean;
  }>;
  excludeFieldsByModel?: Record<string, string[]>;
  excludeQueriesAndMutations?: string[];
  excludeQueriesAndMutationsByModel?: Record<string, string[]>;
}

Generated Type Structure

Base Resolver Types

typescript
import * as Client from "@prisma/client";
import { Context } from "./context";
import { GraphQLResolveInfo } from "graphql";
import { GetAggregateResult } from "@prisma/client/runtime/library";
 
// Core resolver function type
type Resolver<T extends {}, A extends {}, R extends any> = (
  parent: T,
  args: A,
  context: Context,
  info: GraphQLResolveInfo
) => Promise<R> | R;
 
// Utility type for conditional fields
type NoExpand<T> = T extends unknown ? T : never;
 
// AtLeast utility for input constraints
type AtLeast<O extends object, K extends string> = NoExpand<
  O extends unknown
    ?
        | (K extends keyof O ? { [P in K]: O[P] } & O : O)
        | ({ [P in keyof O as P extends K ? K : never]-?: O[P] } & O)
    : never
>;

Type Mapping Rules

Scalar Type Mapping

The generator maps GraphQL scalars to TypeScript types:

typescript
const scalarMapping = {
  String: "string",
  Int: "number",
  Float: "number",
  Boolean: "boolean",
  DateTime: "Date",
  Json: "any",
  Decimal: "number",
  BigInt: "bigint",
  Bytes: "Buffer",
};

Parent Type Resolution

Determines the correct parent type for each resolver:

typescript
function getParentType(typeName: string): string {
  // Root operation types
  if (typeName === "Query" || typeName === "Mutation") {
    return "{}";
  }
 
  // Special Prisma types
  if (typeName === "AffectedRowsOutput") {
    return "Client.Prisma.BatchPayload";
  }
 
  // Model types use Prisma client types
  if (isModelType(typeName)) {
    return `Client.${typeName}`;
  }
 
  // Aggregate types
  if (typeName.startsWith("Aggregate")) {
    return `Client.Prisma.${typeName}`;
  }
 
  // CreateMany return types
  if (
    typeName.startsWith("CreateMany") &&
    typeName.endsWith("AndReturnOutputType")
  ) {
    const modelName = typeName.replace(/^CreateMany|AndReturnOutputType$/g, "");
    return `ReturnType<Client.Prisma.${modelName}Delegate["createManyAndReturn"]>`;
  }
 
  // Default to Prisma types
  return `Client.Prisma.${typeName}`;
}

Output Type Generation

Maps GraphQL output types to TypeScript:

typescript
function getOutputType(field: GraphQLField, isInput = false): string {
  const { type } = field;
 
  switch (type.location) {
    case "scalar":
      const baseType = scalarMapping[type.name] || type.name;
      return type.isList ? `${baseType}[]` : baseType;
 
    case "outputObjectType":
      // Handle special aggregate types
      if (type.name.startsWith("Aggregate")) {
        const modelName = type.name.replace("Aggregate", "");
        return `GetAggregateResult<Client.Prisma.$${modelName}Payload, ${type.name}Args>`;
      }
 
      // Handle CreateMany return types
      if (
        type.name.startsWith("CreateMany") &&
        type.name.endsWith("AndReturnOutputType")
      ) {
        const modelName = type.name.replace(
          /^CreateMany|AndReturnOutputType$/g,
          ""
        );
        return `ReturnType<Client.Prisma.${modelName}Delegate["createManyAndReturn"]>`;
      }
 
      // Standard model types
      const prefix = isInput ? "" : "Client.";
      const typeName =
        type.name === "AffectedRowsOutput"
          ? "Prisma.BatchPayload"
          : isModelType(type.name) && !isInput
            ? type.name
            : `Prisma.${type.name}`;
 
      return `${prefix}${typeName}${type.isList ? "[]" : ""}`;
 
    default:
      return "unknown";
  }
}

Resolver Interface Generation

Main Resolvers Interface

typescript
export type Resolvers = {
  [key: string]: { [key: string]: Resolver<any, any, any> };
} & {
  Query?: Query;
  Mutation?: Mutation;
  User?: User;
  Post?: Post;
  Comment?: Comment;
  AggregateUser?: AggregateUser;
  AggregatePost?: AggregatePost;
  AggregateComment?: AggregateComment;
  // ... other generated types
};

Type-Specific Resolver Interfaces

For each GraphQL type, generate a resolver interface:

typescript
export type Query = {
  [key: string]: Resolver<any, any, any>;
} & {
  findUniqueUser?: Resolver<{}, QueryFindUniqueUserArgs, User | null>;
  findManyUser?: Resolver<{}, QueryFindManyUserArgs, User[]>;
  findManyUserCount?: Resolver<{}, QueryFindManyUserCountArgs, number>;
  findFirstUser?: Resolver<{}, QueryFindFirstUserArgs, User | null>;
  aggregateUser?: Resolver<{}, QueryAggregateUserArgs, AggregateUser>;
 
  findUniquePost?: Resolver<{}, QueryFindUniquePostArgs, Post | null>;
  findManyPost?: Resolver<{}, QueryFindManyPostArgs, Post[]>;
  findManyPostCount?: Resolver<{}, QueryFindManyPostCountArgs, number>;
  findFirstPost?: Resolver<{}, QueryFindFirstPostArgs, Post | null>;
  aggregatePost?: Resolver<{}, QueryAggregatePostArgs, AggregatePost>;
};
 
export type Mutation = {
  [key: string]: Resolver<any, any, any>;
} & {
  createOneUser?: Resolver<{}, MutationCreateOneUserArgs, User>;
  updateOneUser?: Resolver<{}, MutationUpdateOneUserArgs, User>;
  deleteOneUser?: Resolver<{}, MutationDeleteOneUserArgs, User | null>;
  upsertOneUser?: Resolver<{}, MutationUpsertOneUserArgs, User>;
  deleteManyUser?: Resolver<{}, MutationDeleteManyUserArgs, BatchPayload>;
  updateManyUser?: Resolver<{}, MutationUpdateManyUserArgs, BatchPayload>;
};
 
export type User = {
  [key: string]: Resolver<any, any, any>;
} & {
  id?: Resolver<Client.User, {}, string>;
  email?: Resolver<Client.User, {}, string>;
  name?: Resolver<Client.User, {}, string | null>;
  avatar?: Resolver<Client.User, {}, string | null>;
  role?: Resolver<Client.User, {}, Role>;
  posts?: Resolver<Client.User, UserPostsArgs, Post[]>;
  comments?: Resolver<Client.User, UserCommentsArgs, Comment[]>;
};

Arguments Type Generation

Query Arguments

For operations with arguments, generate comprehensive args interfaces:

typescript
export type QueryFindUniqueUserArgs = {
  where: UserWhereUniqueInput;
};
 
export type QueryFindManyUserArgs = {
  where?: UserWhereInput;
  orderBy?: UserOrderByWithRelationInput[];
  cursor?: UserWhereUniqueInput;
  take?: number;
  skip?: number;
  distinct?: UserScalarFieldEnum[];
};
 
export type QueryFindManyUserCountArgs = {
  where?: UserWhereInput;
  orderBy?: UserOrderByWithRelationInput[];
  cursor?: UserWhereUniqueInput;
  take?: number;
  skip?: number;
  distinct?: UserScalarFieldEnum[];
};
 
export type QueryAggregateUserArgs = {
  where?: UserWhereInput;
  orderBy?: UserOrderByWithRelationInput[];
  cursor?: UserWhereUniqueInput;
  take?: number;
  skip?: number;
  _count?: Client.Prisma.UserCountAggregateInputType;
  _avg?: Client.Prisma.UserAvgAggregateInputType;
  _sum?: Client.Prisma.UserSumAggregateInputType;
  _min?: Client.Prisma.UserMinAggregateInputType;
  _max?: Client.Prisma.UserMaxAggregateInputType;
};

Mutation Arguments

typescript
export type MutationCreateOneUserArgs = {
  data: UserCreateInput;
};
 
export type MutationUpdateOneUserArgs = {
  where: UserWhereUniqueInput;
  data: UserUpdateInput;
};
 
export type MutationUpsertOneUserArgs = {
  where: UserWhereUniqueInput;
  create: UserCreateInput;
  update: UserUpdateInput;
};
 
export type MutationDeleteManyUserArgs = {
  where?: UserWhereInput;
};
 
export type MutationUpdateManyUserArgs = {
  where?: UserWhereInput;
  data: UserUpdateManyMutationInput;
};

Relation Field Arguments

typescript
export type UserPostsArgs = {
  where?: PostWhereInput;
  orderBy?: PostOrderByWithRelationInput[];
  cursor?: PostWhereUniqueInput;
  take?: number;
  skip?: number;
  distinct?: PostScalarFieldEnum[];
};
 
export type UserCommentsArgs = {
  where?: CommentWhereInput;
  orderBy?: CommentOrderByWithRelationInput[];
  cursor?: CommentWhereUniqueInput;
  take?: number;
  skip?: number;
  distinct?: CommentScalarFieldEnum[];
};

Input Types Generation

Standard Input Types

typescript
export type UserWhereInput = Client.Prisma.UserWhereInput;
export type UserWhereUniqueInput = Client.Prisma.UserWhereUniqueInput;
export type UserOrderByWithRelationInput =
  Client.Prisma.UserOrderByWithRelationInput;
export type UserCreateInput = Client.Prisma.UserCreateInput;
export type UserUpdateInput = Client.Prisma.UserUpdateInput;
export type UserUpdateManyMutationInput =
  Client.Prisma.UserUpdateManyMutationInput;
 
export type PostWhereInput = Client.Prisma.PostWhereInput;
export type PostWhereUniqueInput = Client.Prisma.PostWhereUniqueInput;
export type PostOrderByWithRelationInput =
  Client.Prisma.PostOrderByWithRelationInput;
export type PostCreateInput = Client.Prisma.PostCreateInput;
export type PostUpdateInput = Client.Prisma.PostUpdateInput;
export type PostUpdateManyMutationInput =
  Client.Prisma.PostUpdateManyMutationInput;

Constraint Handling for Complex Inputs

For input types with field constraints, use the AtLeast utility:

typescript
// Example: If an input requires at least one of several fields
export type UserUpdateInput = AtLeast<
  {
    id?: string;
    email?: string;
    name?: string;
    avatar?: string;
    role?: Role;
    posts?: PostUpdateManyWithoutAuthorNestedInput;
    comments?: CommentUpdateManyWithoutAuthorNestedInput;
  },
  "email" | "name" | "avatar" | "role" | "posts" | "comments"
>;

Enum Types

typescript
export type Role = Client.Role;
export type UserScalarFieldEnum = Client.Prisma.UserScalarFieldEnum;
export type PostScalarFieldEnum = Client.Prisma.PostScalarFieldEnum;
export type CommentScalarFieldEnum = Client.Prisma.CommentScalarFieldEnum;

Aggregate Types

typescript
export type AggregateUser = {
  _count?: UserCountAggregateOutputType;
  _avg?: UserAvgAggregateOutputType;
  _sum?: UserSumAggregateOutputType;
  _min?: UserMinAggregateOutputType;
  _max?: UserMaxAggregateOutputType;
};
 
export type UserCountAggregateOutputType = {
  id: number;
  email: number;
  name: number;
  avatar: number;
  role: number;
  createdAt: number;
  updatedAt: number;
  _all: number;
};
 
// Only include avg/sum for numeric fields
export type UserAvgAggregateOutputType = {
  // No numeric fields in User model
};
 
export type UserSumAggregateOutputType = {
  // No numeric fields in User model
};
 
export type UserMinAggregateOutputType = {
  id?: string;
  email?: string;
  name?: string;
  avatar?: string;
  role?: Role;
  createdAt?: Date;
  updatedAt?: Date;
};
 
export type UserMaxAggregateOutputType = {
  id?: string;
  email?: string;
  name?: string;
  avatar?: string;
  role?: Role;
  createdAt?: Date;
  updatedAt?: Date;
};

Batch Operations

typescript
export type BatchPayload = Client.Prisma.BatchPayload;
 
export type CreateManyUserAndReturnOutputType = ReturnType<
  Client.Prisma.UserDelegate["createManyAndReturn"]
>;

Real-World Example

Sample Usage in Resolvers

typescript
import { Resolvers } from "./resolversTypes";
import { Context } from "./context";
 
const resolvers: Resolvers = {
  Query: {
    // Type-safe resolver - TypeScript knows all argument and return types
    findUniqueUser: async (_parent, { where }, { prisma }) => {
      return prisma.user.findUnique({ where });
    },
 
    findManyUser: async (_parent, args, { prisma }) => {
      return prisma.user.findMany(args);
    },
 
    findManyUserCount: async (_parent, args, { prisma }) => {
      return prisma.user.count(args);
    },
 
    aggregateUser: async (_parent, args, { prisma }) => {
      return prisma.user.aggregate(args);
    },
  },
 
  Mutation: {
    createOneUser: async (_parent, { data }, { prisma }) => {
      return prisma.user.create({ data });
    },
 
    updateOneUser: async (_parent, { where, data }, { prisma }) => {
      return prisma.user.update({ where, data });
    },
 
    deleteOneUser: async (_parent, { where }, { prisma }) => {
      return prisma.user.delete({ where });
    },
  },
 
  User: {
    // Relation resolvers with proper typing
    posts: async (parent, args, { prisma }) => {
      return prisma.user
        .findUnique({
          where: { id: parent.id },
        })
        .posts(args);
    },
 
    comments: async (parent, args, { prisma }) => {
      return prisma.user
        .findUnique({
          where: { id: parent.id },
        })
        .comments(args);
    },
  },
};
 
export default resolvers;

Context Type Definition

The generator expects a Context type to be available:

typescript
// context.ts
import { PrismaClient } from "@prisma/client";
 
export interface Context {
  prisma: PrismaClient;
  user?: {
    id: string;
    email: string;
    role: string;
  };
  req?: any;
  res?: any;
}
 
export const prisma = new PrismaClient();
 
export function createContext(req: any, res?: any): Context {
  return {
    prisma,
    req,
    res,
    // Add user authentication logic here
  };
}

Advanced Type Features

Generic Aggregate Handling

The generator properly handles Prisma's aggregate types:

typescript
// For numeric fields, includes avg and sum
export type PostAvgAggregateOutputType = {
  views?: number;
  likes?: number;
};
 
export type PostSumAggregateOutputType = {
  views?: number;
  likes?: number;
};
 
// Aggregate resolver with proper return type
export type QueryAggregatePostArgs = {
  where?: PostWhereInput;
  orderBy?: PostOrderByWithRelationInput[];
  cursor?: PostWhereUniqueInput;
  take?: number;
  skip?: number;
  _count?: Client.Prisma.PostCountAggregateInputType;
  _avg?: Client.Prisma.PostAvgAggregateInputType;
  _sum?: Client.Prisma.PostSumAggregateInputType;
  _min?: Client.Prisma.PostMinAggregateInputType;
  _max?: Client.Prisma.PostMaxAggregateInputType;
};

CreateMany Return Types

For Prisma's createManyAndReturn operations:

typescript
export type CreateManyUserAndReturnOutputType = ReturnType<
  Client.Prisma.UserDelegate["createManyAndReturn"]
>;
 
export type MutationCreateManyUserAndReturnArgs = {
  data: UserCreateManyInput[];
  skipDuplicates?: boolean;
};
 
// In resolver types
export type Mutation = {
  createManyUserAndReturn?: Resolver<
    {},
    MutationCreateManyUserAndReturnArgs,
    CreateManyUserAndReturnOutputType
  >;
};

Field Exclusion Support

The generator respects field exclusions:

typescript
// If 'email' field is excluded from User
export type User = {
  [key: string]: Resolver<any, any, any>;
} & {
  id?: Resolver<Client.User, {}, string>;
  name?: Resolver<Client.User, {}, string | null>;
  avatar?: Resolver<Client.User, {}, string | null>;
  role?: Resolver<Client.User, {}, Role>;
  // email field is excluded
  posts?: Resolver<Client.User, UserPostsArgs, Post[]>;
  comments?: Resolver<Client.User, UserCommentsArgs, Comment[]>;
};

AI Prompt Example

code
I need to generate TypeScript resolver types for my GraphQL schema using the PalJS patterns.

Please follow the Prisma Resolver Types Generator template with this configuration:
- Output file: src/graphql/resolversTypes.ts
- Exclude sensitive fields: ["password", "resetToken"]
- Models to include: ["User", "Post", "Comment"]

[Include the complete Prisma Resolver Types Generator template from GitHub]

Here's my Prisma schema:
[Include your schema here]

Generate comprehensive TypeScript types for all resolvers, including proper handling of:
- Query and mutation resolvers
- Relation field resolvers
- Aggregate operations
- Input types with constraints
- Enum types
- Batch operations

Benefits and Use Cases

Type Safety

  • Compile-time errors for incorrect resolver signatures
  • IntelliSense support for all resolver arguments and return types
  • Automatic type checking when Prisma schema changes

Developer Experience

  • Auto-completion for resolver functions
  • Type hints for complex nested inputs
  • Refactoring safety when schema evolves

Code Quality

  • Consistent interfaces across all resolvers
  • Self-documenting code through type definitions
  • Prevention of runtime errors through static typing

Integration with Other Tools

GraphQL Code Generator

The generated types work perfectly with GraphQL Code Generator:

yaml
# codegen.yml
generates:
  src/graphql/resolversTypes.ts:
    plugins:
      - typescript
      - typescript-resolvers
    config:
      contextType: ./context#Context
      mappers:
        User: "@prisma/client#User"
        Post: "@prisma/client#Post"
        Comment: "@prisma/client#Comment"

SDL Generator Integration

Use together with the SDL Generator for complete type safety:

  1. Generate SDL schema and resolvers
  2. Generate resolver types
  3. Import types in resolver implementations
  4. Enjoy full type safety

Best Practices

Type Organization

  • Keep types in a separate file (resolversTypes.ts)
  • Import only what you need in resolver files
  • Update types when schema changes by regenerating

Error Handling

typescript
const resolvers: Resolvers = {
  Query: {
    findUniqueUser: async (_parent, { where }, { prisma }) => {
      try {
        return await prisma.user.findUnique({ where });
      } catch (error) {
        throw new Error(`Failed to find user: ${error.message}`);
      }
    },
  },
};

Performance Considerations

  • Types have no runtime cost - they're erased during compilation
  • Use proper return types to enable optimizations
  • Leverage TypeScript's strict mode for better error catching

Troubleshooting

Common Issues

  1. Missing Context Type: Ensure you have a Context interface defined
  2. Prisma Import Errors: Check your @prisma/client installation
  3. Type Mismatches: Regenerate types after schema changes

Type Conflicts

If you encounter type conflicts:

typescript
// Use explicit typing to resolve conflicts
const userResolver: UserResolvers = {
  posts: async (parent, args, { prisma }) => {
    return prisma.user
      .findUnique({
        where: { id: parent.id },
      })
      .posts(args) as Promise<Post[]>;
  },
};

Next Steps

Command Palette

Search for a command to run...