Old to new Nexus
Overview
Note: This guide is not fully up-to-date as it currently uses the deprecated version of the
nexus-plugin-prisma
. While this is still functional, it is recommended to use the newnexus-prisma
library or an alternative code-first GraphQL library like Pothos going forward. If you have any questions, feel free to share them on our Discord.
This upgrade guide describes how to upgrade a project that's based on Prisma 1 and uses nexus
(< v0.12.0) or @nexus/schema
together with nexus-prisma
(< v4.0.0) to implement a GraphQL server.
The code will be upgraded to the latest version of @nexus/schema
. Further, the nexus-prisma
package will be replaced with the new nexus-plugin-prisma
.
The guide assumes that you already went through the guide for upgrading the Prisma ORM layer. This means you already:
- installed the Prisma ORM 2 CLI
- created your Prisma ORM 2 schema
- introspected your database and resolved potential schema incompatibilities
- installed and generated Prisma Client
The guide further assumes that you have a file setup that looks similar to this:
.
├── README.md
├── package.json
├── prisma
│ └── schema.prisma
├── prisma1
│ ├── datamodel.prisma
│ └── prisma.yml
└── src
├── generated
│ ├── nexus-prisma
│ ├── nexus.ts
│ ├── prisma-client
│ └── schema.graphql
├── types.ts
└── index.ts
The important parts are:
- A folder called with
prisma
with your Prisma ORM 2 schema - A folder called
src
with your application code
If this is not what your project structure looks like, you'll need to adjust the instructions in the guide to match your own setup.
1. Upgrade Nexus dependencies
To get started, you can remove the old Nexus and Prisma 1 dependencies:
npm uninstall nexus nexus-prisma prisma-client-lib prisma1
Then, you can install the latest @nexus/schema
dependency in your project:
npm install @nexus/schema
Next, install the Prisma ORM plugin for Nexus which will allow you to expose Prisma ORM models in your GraphQL API (this is the new equivalent of the former nexus-prisma
package):
npm install nexus-plugin-prisma
The nexus-plugin-prisma
dependency bundles all required Prisma ORM dependencies. You should therefore remove the dependencies that you added installed when you upgraded the Prisma ORM layer of your app:
npm uninstall @prisma/cli @prisma/client
Note however that you can still invoke the Prisma ORM 2 CLI with the familiar command:
npx prisma -v
Note: If you see the output of the Prisma 1 CLI when running
npx prisma -v
, be sure to delete yournode_modules
folder and re-runnpm install
.
2. Update the configuration of Nexus and Prisma ORM
To get started, you can remove the old imports that are not needed any more with your new setup:
import { makePrismaSchema, prismaObjectType } from 'nexus-prisma'
import datamodelInfo from './generated/nexus-prisma'
import { prisma } from './generated/prisma-client'
Instead, you now import the following into your application:
import { nexusSchemaPrisma } from 'nexus-plugin-prisma/schema'
import { objectType, makeSchema, queryType, mutationType } from '@nexus/schema'
import { PrismaClient } from '@prisma/client'
Next you need to adjust the code where you currently create your GraphQLSchema
, most likely this is currently happening via the makePrismaSchema
function in your code. Since this function was imported from the removed nexus-prisma
package, you'll need to replace it with the makeSchema
function from the @nexus/schema
package. The way how the Prisma ORM plugin for Nexus is used also changes in the latest version.
Here's an example for such a configuration:
const schema = makePrismaSchema({
const schema = makeSchema({
// Provide all the GraphQL types we've implemented
types: [Query, Mutation, UserUniqueInput, User, Post, Category, Profile],
// Configure the interface to Prisma
prisma: {
datamodelInfo,
client: prisma,
},
plugins: [nexusSchemaPrisma({
experimentalCRUD: true,
})],
// Specify where Nexus should put the generated files
outputs: {
schema: path.join(__dirname, './generated/schema.graphql'),
typegen: path.join(__dirname, './generated/nexus.ts'),
},
// Configure nullability of input arguments: All arguments are non-nullable by default
nonNullDefaults: {
input: false,
output: false,
},
// Configure automatic type resolution for the TS representations of the associated types
typegenAutoConfig: {
sources: [
{
source: path.join(__dirname, './types.ts'),
alias: 'types',
},
],
contextType: 'types.Context',
},
})
If you previously typed the GraphQL context
object that's passed through your resolver chain, you need to adjust the type like so:
import { Prisma } from './generated/prisma-client'
import { PrismaClient } from '@prisma/client'
export interface Context {
prisma: Prisma
prisma: PrismaClient
}
3. Migrate your GraphQL types
Here's a quick overview of the main differences between the two approaches of creating GraphQL types with the latest versions of @nexus/schema
and nexus-plugin-prisma
.
- The
prismaObjectType
function is not available any more, all types are created with Nexus'objectType
function. - To expose Prisma models via Nexus, you can use the
t.model
property which is added to thet
argument that's passed into Nexus'definition
functions.t.model
gives you access to the properties of a Prisma model and lets you expose them. - Exposing CRUD operations for Prisma models via Nexus follows a similar approach. These are exposed via
t.crud
in thedefinition
functions of yourqueryType
andmutationType
types.
3.1. Migrating the Post
type
Type definition with the previous nexus-prisma
package
In the sample app, the User
type is defined as follows:
const User = prismaObjectType({
name: 'User',
definition(t) {
t.prismaFields([
'id',
'name',
'email',
'jsonData',
'role'
{
name: 'posts',
args: [], // remove the arguments from the `posts` field of the `User` type in the Prisma schema
},
])
},
})
Type definition with the latest version of @nexus/schema
and the nexus-plugin-prisma
With the latest version of @nexus/schema
, you can now access the objectType
function on your main schema
instance and expose all fields from the Prisma model like so:
const User = objectType({
name: 'User',
definition(t) {
t.model.id()
t.model.name()
t.model.email()
t.model.jsonData()
t.model.role()
t.model.posts({
pagination: false,
ordering: false,
filtering: false,
})
t.model.profile()
},
})
Note that t.model
looks at the name
attribute in the object that's passed as an argument to the objectType
function and matches it against the models in your Prisma schema. In this case, it's matched against the User
model. Therefore, t.model
exposes functions that are named after the fields of the User
model.
At this point, you might see errors on the relation fields posts
and profile
, e.g.:
//delete-next-line
Missing type Post, did you forget to import a type to the root query?
This is because you didn't add the Post
and Profile
types to the GraphQL schema yet, the errors will go away once these types are part of the GraphQL schema as well!
3.2. Migrating the Post
type
Type definition with the previous nexus-prisma
package
In the sample app, the Post
type is defined as follows:
const Post = prismaObjectType({
name: 'Post',
definition(t) {
t.prismaFields(['*'])
},
})
The asterisk in prismaFields
means that all Prisma fields are exposed.
Type definition with the latest version of @nexus/schema
and the nexus-plugin-prisma
With the latest version of @nexus/schema
, you need to expose all fields explicitly, there's no option to just expose everything from a Prisma model.
Therefore, the new definition of Post
must explicitly list all its fields:
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.title()
t.model.content()
t.model.published()
t.model.author()
t.model.categories()
},
})
Note that t.model
looks at the name
attribute and matches it against the models in your Prisma schema. In this case, it's matched against the Post
model. Therefore, t.model
exposes functions that are named after the fields of the Post
model.
3.3. Migrating the Profile
type
Type definition with the previous nexus-prisma
package
In the sample app, the Profile
type is defined as follows:
const Profile = prismaObjectType({
name: 'Profile',
definition(t) {
t.prismaFields(['*'])
},
})
The asterisk in prismaFields
means that all Prisma fields are exposed.
Type definition with the latest version of @nexus/schema
and the nexus-plugin-prisma
With the latest version of @nexus/schema
, you need to expose all fields explicitly, there's no option to just expose everything from a Prisma model.
Therefore, the new definition of Profile
must explicitly list all its fields:
const Profile = objectType({
name: 'Profile',
definition(t) {
t.model.id()
t.model.bio()
t.model.user()
t.model.userId()
},
})
Note that t.model
looks at the name
attribute and matches it against the models in your Prisma schema. In this case, it's matched against the Profile
model. Therefore, t.model
exposes functions that are named after the fields of the Profile
model.
3.4. Migrating the Category
type
Type definition with the previous nexus-prisma
package
In the sample app, the Category
type is defined as follows:
const Category = prismaObjectType({
name: 'Category',
definition(t) {
t.prismaFields(['*'])
},
})
The asterisk in prismaFields
means that all Prisma ORM fields are exposed.
Type definition with the latest version of @nexus/schema
and the nexus-plugin-prisma
With the latest version of @nexus/schema
, you need to expose all fields explicitly, there's no option to just expose everything from a Prisma model.
Therefore, the new definition of Category
must explicitly list all its fields:
const Category = objectType({
name: 'Category',
definition(t) {
t.model.id()
t.model.name()
t.model.posts({
pagination: true,
ordering: true,
filtering: true,
})
},
})
Note that t.model
looks at the name
attribute and matches it against the models in your Prisma schema. In this case, it's matched against the Category
model. Therefore, t.model
exposes functions that are named after the fields of the Category
model.
4. Migrate GraphQL operations
As a next step, you can start migrating all the GraphQL queries and mutations from the "previous" GraphQL API to the new one.
For this guide, the following sample GraphQL operations will be used:
input UserUniqueInput {
id: String
email: String
}
type Query {
posts(searchString: String): [Post!]!
user(userUniqueInput: UserUniqueInput!): User
users(where: UserWhereInput, orderBy: Enumerable<UserOrderByInput>, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
}
type Mutation {
createUser(data: UserCreateInput!): User!
createDraft(title: String!, content: String, authorId: ID!): Post
updateBio(userUniqueInput: UserUniqueInput!, bio: String!): User
addPostToCategories(postId: String!, categoryIds: [String!]!): Post
}
4.1. Migrate GraphQL queries
In this section, you'll migrate all GraphQL queries from the previous version of nexus
and nexus-prisma
to the latest version of @nexus/schema
and the nexus-plugin-prisma
.
4.1.1. Migrate the users
query
In our sample API, the users
query from the sample GraphQL schema is implemented as follows.
const Query = prismaObjectType({
name: 'Query',
definition(t) {
t.prismaFields(['users'])
},
})
To get the same behavior with the new Nexus, you need to call the users
function on t.crud
:
schema.queryType({
definition(t) {
t.crud.users({
filtering: true,
ordering: true,
pagination: true,
})
},
})
Recall that the crud
property is added to t
by the nexus-plugin-prisma
(using the same mechanism as for t.model
).
4.1.2. Migrate the posts(searchString: String): [Post!]!
query
In the sample API, the posts
query is implemented as follows:
queryType({
definition(t) {
t.list.field('posts', {
type: 'Post',
args: {
searchString: stringArg({ nullable: true }),
},
resolve: (parent, { searchString }, context) => {
return context.prisma.posts({
where: {
OR: [
{ title_contains: searchString },
{ content_contains: searchString },
],
},
})
},
})
},
})
The only thing that needs to be updated for this query is the call to Prisma ORM since the new Prisma Client API looks a bit different from the one used in Prisma 1.
queryType({
definition(t) {
t.list.field('posts', {
type: 'Post',
args: {
searchString: stringArg({ nullable: true }),
},
resolve: (parent, { searchString }, context) => {
return context.prisma.post.findMany({
where: {
OR: [
{ title: { contains: searchString } },
{ content: { contains: searchString } },
],
},
})
},
})
},
})
Notice that the db
object is automatically attached to the context
by the nexus-plugin-prisma
. It represents an instance of your PrismaClient
which enables you to send queries to your database inside your resolvers.
4.1.3. Migrate the user(uniqueInput: UserUniqueInput): User
query
In the sample API, the user
query is implemented as follows:
inputObjectType({
name: 'UserUniqueInput',
definition(t) {
t.string('id')
t.string('email')
},
})
queryType({
definition(t) {
t.field('user', {
type: 'User',
args: {
userUniqueInput: schema.arg({
type: 'UserUniqueInput',
nullable: false,
}),
},
resolve: (_, args, context) => {
return context.prisma.user({
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
})
},
})
},
})
You now need to adjust the call to your prisma
instance since the new Prisma Client API looks a bit different from the one used in Prisma 1.
const Query = queryType({
definition(t) {
t.field('user', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
},
resolve: (_, args, context) => {
return context.prisma.user.findUnique({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
})
},
})
},
})
4.2. Migrate GraphQL mutations
In this section, you'll migrate the GraphQL mutations from the sample schema to the latest versions of @nexus/schema
and the nexus-plugin-prisma
.
4.2.1. Migrate the createUser
mutation
In our sample API, the createUser
mutation from the sample GraphQL schema is implemented as follows.
const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
t.prismaFields(['createUser'])
},
})
To get the same behavior with the latest versions of @nexus/schema
and the nexus-plugin-prisma
, you need to call the createOneUser
function on t.crud
and pass an alias
in order to rename the field in your GraphQL schema to createUser
(otherwise it would be called createOneUser
, after the function that's used):
const Query = queryType({
definition(t) {
t.crud.createOneUser({
alias: 'createUser',
})
},
})
Recall that the crud
property is added to t
by the nexus-plugin-prisma
(using the same mechanism as for t.model
).
4.2.2. Migrate the createDraft(title: String!, content: String, authorId: String!): Post!
query
In the sample app, the createDraft
mutation implemented as follows.
mutationType({
definition(t) {
t.field('createDraft', {
type: 'Post',
args: {
title: stringArg({ nullable: false }),
content: stringArg(),
authorId: stringArg({ nullable: false }),
},
resolve: (_, args, context) => {
return context.prisma.createPost({
title: args.title,
content: args.content,
author: {
connect: { id: args.authorId },
},
})
},
})
},
})
You now need to adjust the call to your prisma
instance since the new Prisma Client API looks a bit different from the one used in Prisma 1.
const Mutation = mutationType({
definition(t) {
t.field('createDraft', {
type: 'Post',
args: {
title: stringArg({ nullable: false }),
content: stringArg(),
authorId: stringArg({ nullable: false }),
},
resolve: (_, args, context) => {
return context.prisma.post.create({
data: {
title: args.title,
content: args.content,
author: {
connect: { id: args.authorId },
},
},
})
},
})
},
})
4.2.3. Migrate the updateBio(bio: String, userUniqueInput: UserUniqueInput!): User
mutation
In the sample API, the updateBio
mutation is defined and implemented as follows.
mutationType({
definition(t) {
t.field('updateBio', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
bio: stringArg(),
},
resolve: (_, args, context) => {
return context.prisma.updateUser({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
data: {
profile: {
create: { bio: args.bio },
},
},
})
},
})
},
})
You now need to adjust the call to your prisma
instance since the new Prisma Client API looks a bit different from the one used in Prisma 1.
const Mutation = mutationType({
definition(t) {
t.field('updateBio', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
bio: stringArg(),
},
resolve: (_, args, context) => {
return context.prisma.user.update({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
data: {
profile: {
create: { bio: args.bio },
},
},
})
},
})
},
})
4.2.4. Migrate the addPostToCategories(postId: String!, categoryIds: [String!]!): Post
mutation
In the sample API, the addPostToCategories
mutation is defined and implemented as follows.
mutationType({
definition(t) {
t.field('addPostToCategories', {
type: 'Post',
args: {
postId: stringArg({ nullable: false }),
categoryIds: stringArg({
list: true,
nullable: false,
}),
},
resolve: (_, args, context) => {
const ids = args.categoryIds.map((id) => ({ id }))
return context.prisma.updatePost({
where: {
id: args.postId,
},
data: {
categories: { connect: ids },
},
})
},
})
},
})
You now need to adjust the call to your prisma
instance since the new Prisma Client API looks a bit different from the one used in Prisma 1.
const Mutation = mutationType({
definition(t) {
t.field('addPostToCategories', {
type: 'Post',
args: {
postId: stringArg({ nullable: false }),
categoryIds: stringArg({
list: true,
nullable: false,
}),
},
resolve: (_, args, context) => {
const ids = args.categoryIds.map((id) => ({ id }))
return context.prisma.post.update({
where: {
id: args.postId,
},
data: {
categories: { connect: ids },
},
})
},
})
},
})
5. Cleaning up
5.1. Clean up npm dependencies
If you haven't already, you can now uninstall dependencies that were related to the Prisma 1 setup:
npm uninstall prisma1 prisma-client-lib
5.2. Delete unused files
Next, delete the files of your Prisma 1 setup:
rm -rf src/generated
rm -rf prisma1
5.3. Stop the Prisma ORM server
Finally, you can stop running your Prisma ORM server.