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.
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
- Learn about GraphQL Modules Generator for modular architecture
- Explore the SDL Generator for schema-first development
- Check out Nexus documentation for advanced features
- View complete examples on GitHub