As your project grows and the GraphQL API becomes more complex, manually creating
schema
andresolvers
can reduce development efficiency, as both must have the same structure. Otherwise, errors and unpredictable behavior may occur. When theschema
andresolvers
change, these two components can easily get out of sync. GraphQL schemas are defined as strings, so for SDL (Schema Definition Language), there is no code auto-completion or build-time error checking.
It is recommended to use Pothos to build GraphQL schemas, as it allows you to construct APIs using a programming language, which has several advantages:
Install dependencies
npm install @pothos/plugin-prisma @pothos/core
Update the prisma schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma"
}
generator pothos {
provider = "prisma-pothos-types"
}
// graphql/builder.ts
import SchemaBuilder from '@pothos/core'
import PrismaPlugin from '@pothos/plugin-prisma'
import prisma from '@/lib/prisma'
import { DateTimeResolver } from 'graphql-scalars'
export const builder = new SchemaBuilder<{
PrismaTypes: any
Context: {
prisma: typeof prisma
}
}>({
plugins: [PrismaPlugin],
prisma: {
client: prisma,
dmmf: (prisma as any)._dmmf,
},
})
Each model can be separated into its own file, defining its own schema.
// graphql/schema.ts
import { builder } from './builder'
import './types/User'
import './types/Post'
export const schema = builder.toSchema()
// src/api/graphql/route.ts
import { createYoga } from 'graphql-yoga'
import { schema } from '../../../../graphql/schema'
import { NextRequest } from 'next/server'
const { handleRequest } = createYoga({
schema,
})
export async function GET(request: NextRequest) {
return handleRequest(request, {} as any)
}
export async function POST(request: NextRequest) {
return handleRequest(request, {} as any)
}
With the help of Pothos' Prisma plugin, you can use prismaObject
to directly reference field types defined in Prisma, with automatic editor hints. You can also use objectType
to define new object types.
// types/Post.ts
import prisma from '@/lib/prisma'
import { builder } from '../builder'
builder.prismaObject('Post', {
fields: (t: any) => ({
id: t.exposeID('id'),
title: t.exposeString('title'),
summary: t.exposeString('summary'),
content: t.exposeString('content'),
createdAt: t.expose('createdAt', { type: 'DateTime' }),
updatedAt: t.expose('updatedAt', { type: 'DateTime' }),
createdById: t.exposeID('createdById'),
createdBy: t.relation('createdBy'),
}),
})
// Define paginated result type
builder.objectType('PaginatedPosts' as any, {
fields: (t) => ({
posts: t.field({
type: ['Post'] as any,
resolve: (parent) => parent.posts,
}),
postsCount: t.int({
resolve: (parent) => parent.postsCount,
}),
}),
})
// Define query types
builder.queryType({
fields: (t) => ({
// Query a single post
post: t.field({
type: 'Post' as any,
args: {
id: t.arg.id(),
},
resolve: async (_root: any, args: any, ctx: any) => {
const { id } = args
const post = await ctx.prisma.post.findUnique({
where: { id },
})
return post
},
}),
// Paginated query
paginatedPosts: t.field({
type: 'PaginatedPosts' as any,
args: {
skip: t.arg.int({ defaultValue: 0 }),
take: t.arg.int({ defaultValue: 10 }),
},
resolve: async (_root: any, args: any, ctx: any) => {
const { skip, take } = args
// Get total count
const totalCount = await ctx.prisma.post.count()
// Get paginated data
const posts = await ctx.prisma.post.findMany({
skip: skip || 0,
take: take || 10,
orderBy: {
id: 'desc', // Sort by ID in descending order, newest first
},
})
return {
posts,
postsCount: totalCount,
}
},
} as any),
}),
})
// Define mutation type for adding a post
builder.mutationType({
fields: (t: any) => ({
addPost: t.field({
type: 'Post' as any,
args: {
title: t.arg.string(),
summary: t.arg.string(),
content: t.arg.string(),
},
resolve: async (_root: any, args: any, ctx: any) => {
if (ctx.user?.role !== 'ADMIN') {
throw new Error('you are not admin')
}
const { title, summary, content } = args
const post = await ctx.prisma.post.create({
data: { title, summary, content, createdById: ctx.user.id },
})
return post
},
}),
}),
} as any)