PJPalJS

Prisma Nexus Generator

Generate Nexus GraphQL schema with type-safe resolvers. This generator creates complete Nexus object types, queries, mutations, and input types from your Prisma schema.

View Template on GitHub

Overview

The Prisma Nexus Generator creates type-safe GraphQL schemas using the Nexus framework. It generates:

  • Nexus object type definitions for all Prisma models
  • Type-safe GraphQL resolvers with full TypeScript support
  • Complete query and mutation implementations for CRUD operations
  • Input types and enums for GraphQL operations
  • Proper TypeScript/JavaScript module structure for scalable development

Configuration Options

typescript
interface NexusGeneratorOptions {
  output?: string; // Output directory (default: "src/graphql")
  prismaName?: string; // Prisma client instance name (default: "prisma")
  excludeFields?: string[]; // Fields to exclude globally
  excludeModels?: Array<{
    // Models with selective exclusions
    name: string;
    queries?: boolean;
    mutations?: boolean;
  }>;
  excludeFieldsByModel?: Record<string, string[]>;
  excludeQueriesAndMutations?: string[];
  excludeQueriesAndMutationsByModel?: Record<string, string[]>;
  disableQueries?: boolean; // Disable all query generation
  disableMutations?: boolean; // Disable all mutation generation
  javaScript?: boolean; // Generate JS instead of TS
}

Generated File Structure

code
src/graphql/
├── types.ts              # Nexus object type definitions
├── inputs.ts             # Input type definitions
├── queries.ts            # Query field definitions
├── mutations.ts          # Mutation field definitions
├── resolvers.ts          # Resolver implementations
└── index.ts              # Combined exports and schema

Generation Patterns

1. Object Types (types.ts)

typescript
import { objectType } from "@nexus/schema";
 
export const User = objectType({
  name: "User",
  definition(t) {
    t.nonNull.field("id", { type: "String" });
    t.field("email", { type: "String" });
    t.field("name", { type: "String" });
    t.field("avatar", { type: "String" });
    t.nonNull.field("createdAt", { type: "DateTime" });
    t.nonNull.field("updatedAt", { type: "DateTime" });
 
    // Relations
    t.list.field("posts", {
      type: "Post",
      resolve: (parent, args, { prisma }) => {
        return prisma.user.findUnique({ where: { id: parent.id } }).posts();
      },
    });
 
    t.list.field("comments", {
      type: "Comment",
      resolve: (parent, args, { prisma }) => {
        return prisma.user.findUnique({ where: { id: parent.id } }).comments();
      },
    });
  },
});

2. Input Types (inputs.ts)

typescript
import { inputObjectType } from "@nexus/schema";
 
export const UserCreateInput = inputObjectType({
  name: "UserCreateInput",
  definition(t) {
    t.string("id");
    t.nonNull.string("email");
    t.string("name");
    t.string("avatar");
    t.field("posts", { type: "PostCreateNestedManyWithoutAuthorInput" });
    t.field("comments", { type: "CommentCreateNestedManyWithoutAuthorInput" });
  },
});
 
export const UserUpdateInput = inputObjectType({
  name: "UserUpdateInput",
  definition(t) {
    t.field("email", { type: "StringFieldUpdateOperationsInput" });
    t.field("name", { type: "NullableStringFieldUpdateOperationsInput" });
    t.field("avatar", { type: "NullableStringFieldUpdateOperationsInput" });
    t.field("posts", { type: "PostUpdateManyWithoutAuthorNestedInput" });
    t.field("comments", { type: "CommentUpdateManyWithoutAuthorNestedInput" });
  },
});
 
export const UserWhereInput = inputObjectType({
  name: "UserWhereInput",
  definition(t) {
    t.list.field("AND", { type: "UserWhereInput" });
    t.list.field("OR", { type: "UserWhereInput" });
    t.list.field("NOT", { type: "UserWhereInput" });
    t.field("id", { type: "StringFilter" });
    t.field("email", { type: "StringFilter" });
    t.field("name", { type: "StringNullableFilter" });
    t.field("avatar", { type: "StringNullableFilter" });
    t.field("createdAt", { type: "DateTimeFilter" });
    t.field("updatedAt", { type: "DateTimeFilter" });
    t.field("posts", { type: "PostListRelationFilter" });
    t.field("comments", { type: "CommentListRelationFilter" });
  },
});

3. Query Fields (queries.ts)

typescript
import { queryField, list, nonNull, arg } from "@nexus/schema";
 
export const findUniqueUser = queryField("findUniqueUser", {
  type: "User",
  args: {
    where: nonNull(arg({ type: "UserWhereUniqueInput" })),
  },
  resolve: (parent, { where }, { prisma }) => {
    return prisma.user.findUnique({ where });
  },
});
 
export const findManyUser = queryField("findManyUser", {
  type: list("User"),
  args: {
    where: arg({ type: "UserWhereInput" }),
    orderBy: list(arg({ type: "UserOrderByWithRelationInput" })),
    cursor: arg({ type: "UserWhereUniqueInput" }),
    take: arg({ type: "Int" }),
    skip: arg({ type: "Int" }),
  },
  resolve: (parent, args, { prisma }) => {
    return prisma.user.findMany(args);
  },
});
 
export const findManyUserCount = queryField("findManyUserCount", {
  type: "Int",
  args: {
    where: arg({ type: "UserWhereInput" }),
    orderBy: list(arg({ type: "UserOrderByWithRelationInput" })),
    cursor: arg({ type: "UserWhereUniqueInput" }),
    take: arg({ type: "Int" }),
    skip: arg({ type: "Int" }),
  },
  resolve: (parent, args, { prisma }) => {
    return prisma.user.count(args);
  },
});

4. Mutation Fields (mutations.ts)

typescript
import { mutationField, nonNull, arg } from "@nexus/schema";
 
export const createOneUser = mutationField("createOneUser", {
  type: "User",
  args: {
    data: nonNull(arg({ type: "UserCreateInput" })),
  },
  resolve: (parent, { data }, { prisma }) => {
    return prisma.user.create({ data });
  },
});
 
export const updateOneUser = mutationField("updateOneUser", {
  type: "User",
  args: {
    where: nonNull(arg({ type: "UserWhereUniqueInput" })),
    data: nonNull(arg({ type: "UserUpdateInput" })),
  },
  resolve: (parent, { where, data }, { prisma }) => {
    return prisma.user.update({ where, data });
  },
});
 
export const deleteOneUser = mutationField("deleteOneUser", {
  type: "User",
  args: {
    where: nonNull(arg({ type: "UserWhereUniqueInput" })),
  },
  resolve: (parent, { where }, { prisma }) => {
    return prisma.user.delete({ where });
  },
});

Real-World Example

Sample Prisma Schema

prisma
// schema.prisma
model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  avatar    String?
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  posts    Post[]
  comments Comment[]
}
 
model Post {
  id        String   @id @default(uuid())
  title     String
  content   String?
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  authorId String
  author   User      @relation(fields: [authorId], references: [id])
  comments Comment[]
}
 
model Comment {
  id        String   @id @default(uuid())
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
 
  postId   String
  post     Post   @relation(fields: [postId], references: [id])
  authorId String
  author   User   @relation(fields: [authorId], references: [id])
}
 
enum Role {
  USER
  ADMIN
  MODERATOR
}

AI Prompt

code
I need to generate a Nexus GraphQL schema for my blog application using the PalJS patterns.

Please follow the Prisma Nexus Generator template with this configuration:
- Output directory: src/graphql
- Exclude fields: ["createdAt", "updatedAt"]
- Models to generate: ["User", "Post", "Comment"]
- Generate TypeScript (not JavaScript)
- Prisma client name: "prisma"

[Include the complete Prisma Nexus Generator template from GitHub]

Here's my Prisma schema:
[Include the schema above]

Generate the Nexus files following the template patterns.

Generated Schema Setup (index.ts)

typescript
import { makeSchema } from "@nexus/schema";
import { nexusPrisma } from "nexus-plugin-prisma";
import path from "path";
 
// Import all types
import * as types from "./types";
import * as inputs from "./inputs";
import * as queries from "./queries";
import * as mutations from "./mutations";
 
export const schema = makeSchema({
  types: [types, inputs, queries, mutations],
  plugins: [
    nexusPrisma({
      experimentalCRUD: true,
    }),
  ],
  outputs: {
    schema: path.join(process.cwd(), "schema.graphql"),
    typegen: path.join(process.cwd(), "generated", "nexus.ts"),
  },
  contextType: {
    module: path.join(process.cwd(), "src", "context.ts"),
    export: "Context",
  },
  sourceTypes: {
    modules: [
      {
        module: "@prisma/client",
        alias: "prisma",
      },
    ],
  },
});

Context Setup

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

Server Setup (Express + Apollo)

typescript
// src/server.ts
import { ApolloServer } from "apollo-server-express";
import express from "express";
import { schema } from "./graphql";
import { createContext } from "./context";
 
async function startServer() {
  const app = express();
 
  const server = new ApolloServer({
    schema,
    context: ({ req }) => createContext(req),
    introspection: true,
    playground: true,
  });
 
  await server.start();
  server.applyMiddleware({ app, path: "/graphql" });
 
  const port = process.env.PORT || 4000;
 
  app.listen(port, () => {
    console.log(
      `🚀 Server ready at http://localhost:${port}${server.graphqlPath}`
    );
  });
}
 
startServer().catch((error) => {
  console.error("Error starting server:", error);
});

Advanced Features

Custom Resolvers with Business Logic

typescript
// Enhanced user resolver with authorization
export const User = objectType({
  name: "User",
  definition(t) {
    t.nonNull.field("id", { type: "String" });
    t.field("email", {
      type: "String",
      resolve: (parent, args, { user }) => {
        // Only return email if it's the user's own data or admin
        if (user?.id === parent.id || user?.role === "ADMIN") {
          return parent.email;
        }
        return null;
      },
    });
    t.field("name", { type: "String" });
 
    // Computed field
    t.field("fullName", {
      type: "String",
      resolve: (parent) => {
        return `${parent.firstName} ${parent.lastName}`;
      },
    });
 
    // Relation with custom filtering
    t.list.field("posts", {
      type: "Post",
      args: {
        published: arg({ type: "Boolean" }),
      },
      resolve: (parent, { published }, { prisma }) => {
        return prisma.user
          .findUnique({
            where: { id: parent.id },
          })
          .posts({
            where: published !== undefined ? { published } : undefined,
          });
      },
    });
  },
});

Input Validation

typescript
import { mutationField, nonNull, arg } from "@nexus/schema";
import { UserInputError } from "apollo-server-express";
 
export const createOneUser = mutationField("createOneUser", {
  type: "User",
  args: {
    data: nonNull(arg({ type: "UserCreateInput" })),
  },
  resolve: async (parent, { data }, { prisma }) => {
    // Validation
    if (!data.email || !data.email.includes("@")) {
      throw new UserInputError("Valid email is required");
    }
 
    if (data.name && data.name.length < 2) {
      throw new UserInputError("Name must be at least 2 characters long");
    }
 
    // Check for existing user
    const existingUser = await prisma.user.findUnique({
      where: { email: data.email },
    });
 
    if (existingUser) {
      throw new UserInputError("User with this email already exists");
    }
 
    return prisma.user.create({ data });
  },
});

Pagination and Filtering

typescript
export const findManyUser = queryField("findManyUser", {
  type: list("User"),
  args: {
    where: arg({ type: "UserWhereInput" }),
    orderBy: list(arg({ type: "UserOrderByWithRelationInput" })),
    cursor: arg({ type: "UserWhereUniqueInput" }),
    take: arg({ type: "Int" }),
    skip: arg({ type: "Int" }),
    // Custom search argument
    search: arg({ type: "String" }),
  },
  resolve: (parent, args, { prisma }) => {
    const { search, ...restArgs } = args;
 
    // Add search functionality
    if (search) {
      restArgs.where = {
        ...restArgs.where,
        OR: [
          { name: { contains: search, mode: "insensitive" } },
          { email: { contains: search, mode: "insensitive" } },
        ],
      };
    }
 
    return prisma.user.findMany(restArgs);
  },
});

Type Safety Benefits

Generated Types

The Nexus generator creates TypeScript types that ensure type safety:

typescript
// Generated types in nexus.ts
export interface NexusGenRootTypes {
  User: {
    id: string;
    email: string | null;
    name: string | null;
    avatar: string | null;
    createdAt: Date;
    updatedAt: Date;
  };
  Post: {
    // ... post types
  };
}
 
export interface NexusGenArgTypes {
  Mutation: {
    createOneUser: {
      data: NexusGenInputs["UserCreateInput"];
    };
    updateOneUser: {
      where: NexusGenInputs["UserWhereUniqueInput"];
      data: NexusGenInputs["UserUpdateInput"];
    };
  };
}

Resolver Type Safety

typescript
// Type-safe resolver with full IntelliSense
export const createOneUser = mutationField("createOneUser", {
  type: "User",
  args: {
    data: nonNull(arg({ type: "UserCreateInput" })),
  },
  resolve: (
    parent: {}, // Root type
    args: { data: UserCreateInput }, // Typed arguments
    context: Context // Typed context
  ): Promise<User> => {
    // Return type
    return context.prisma.user.create({ data: args.data });
  },
});

Performance Optimization

DataLoader Integration

typescript
import DataLoader from "dataloader";
 
// In context.ts
export function createContext(req: any): Context {
  return {
    prisma,
    // DataLoaders for batching
    loaders: {
      userLoader: new DataLoader(async (userIds: string[]) => {
        const users = await prisma.user.findMany({
          where: { id: { in: userIds } },
        });
        return userIds.map((id) => users.find((user) => user.id === id));
      }),
      postsByUserLoader: new DataLoader(async (userIds: string[]) => {
        const posts = await prisma.post.findMany({
          where: { authorId: { in: userIds } },
        });
        return userIds.map((userId) =>
          posts.filter((post) => post.authorId === userId)
        );
      }),
    },
  };
}
 
// In resolvers
export const User = objectType({
  name: "User",
  definition(t) {
    t.list.field("posts", {
      type: "Post",
      resolve: (parent, args, { loaders }) => {
        return loaders.postsByUserLoader.load(parent.id);
      },
    });
  },
});

Selective Field Loading

typescript
import { selectField } from "nexus-plugin-prisma/select";
 
export const findUniqueUser = queryField("findUniqueUser", {
  type: "User",
  args: {
    where: nonNull(arg({ type: "UserWhereUniqueInput" })),
  },
  resolve: (parent, { where }, { prisma }, info) => {
    return prisma.user.findUnique({
      where,
      ...selectField(info), // Only select requested fields
    });
  },
});

Testing

Unit Testing Resolvers

typescript
// tests/resolvers.test.ts
import { createTestContext } from "./__helpers";
 
const ctx = createTestContext();
 
describe("User resolvers", () => {
  test("createOneUser creates a new user", async () => {
    const user = await ctx.client.request(`
      mutation {
        createOneUser(data: {
          email: "test@example.com"
          name: "Test User"
        }) {
          id
          email
          name
        }
      }
    `);
 
    expect(user.createOneUser).toMatchObject({
      email: "test@example.com",
      name: "Test User",
    });
  });
 
  test("findManyUser returns paginated results", async () => {
    const users = await ctx.client.request(`
      query {
        findManyUser(take: 5) {
          id
          email
          name
        }
      }
    `);
 
    expect(users.findManyUser).toHaveLength(5);
  });
});

Deployment

Production Setup

typescript
// src/server.ts (production)
import { ApolloServer } from "apollo-server-express";
import express from "express";
import { schema } from "./graphql";
import { createContext } from "./context";
 
const server = new ApolloServer({
  schema,
  context: createContext,
  introspection: process.env.NODE_ENV !== "production",
  playground: process.env.NODE_ENV !== "production",
  formatError: (error) => {
    // Log error for debugging
    console.error("GraphQL Error:", error);
 
    // Return sanitized error in production
    if (process.env.NODE_ENV === "production") {
      return new Error("Internal server error");
    }
 
    return error;
  },
});
 
const app = express();
server.applyMiddleware({ app });
 
app.listen(process.env.PORT || 4000);

Best Practices

Code Organization

  • Separate concerns - Keep types, queries, and mutations in separate files
  • Use meaningful names - Follow GraphQL naming conventions
  • Group related functionality - Organize by domain/feature
  • Type everything - Leverage TypeScript fully

Performance

  • Use DataLoaders - Prevent N+1 query problems
  • Implement pagination - Handle large datasets properly
  • Add field selection - Only fetch requested data
  • Cache when appropriate - Use Redis or in-memory caching

Security

  • Validate inputs - Always validate user inputs
  • Implement authorization - Check permissions in resolvers
  • Rate limiting - Prevent abuse with rate limiting
  • Query complexity analysis - Limit expensive queries

Next Steps

Command Palette

Search for a command to run...