Overview

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 layer. This means you already:

  • installed the Prisma 2 CLI
  • created your Prisma 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 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 plugin for Nexus which will allow you to expose Prisma 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 dependencies. You should therefore remove the dependencies that you added installed when you upgraded the Prisma layer of your app:

npm uninstall @prisma/cli @prisma/client

Note however that you can still invoke the Prisma 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 your node_modules folder and re-run npm install.

2. Update the configuration of Nexus and Prisma

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 plugin for Nexus is used also changes in the latest version.

Here's an example for such a configuration:

./src/index.ts
- const schema = makePrismaSchema({
+ const schema = makeSchema({
3
4 // Provide all the GraphQL types we've implemented
5 types: [Query, Mutation, UserUniqueInput, User, Post, Category, Profile],
6
7 // Configure the interface to Prisma
- prisma: {
- datamodelInfo,
- client: prisma,
- },
+ plugins: [nexusSchemaPrisma({
+ experimentalCRUD: true,
+ })],
15
16 // Specify where Nexus should put the generated files
17 outputs: {
18 schema: path.join(__dirname, './generated/schema.graphql'),
19 typegen: path.join(__dirname, './generated/nexus.ts'),
20 },
21
22 // Configure nullability of input arguments: All arguments are non-nullable by default
23 nonNullDefaults: {
24 input: false,
25 output: false,
26 },
27
28 // Configure automatic type resolution for the TS representations of the associated types
29 typegenAutoConfig: {
30 sources: [
31 {
32 source: path.join(__dirname, './types.ts'),
33 alias: 'types',
34 },
35 ],
36 contextType: 'types.Context',
37 },
38})

If you previously typed the GraphQL context object that's passed through your resolver chain, you need to adjust the type like so:

./src/types.ts
- import { Prisma } from './generated/prisma-client'
+ import { PrismaClient } from '@prisma/client'
3
4export interface Context {
- prisma: Prisma
+ prisma: PrismaClient
7}

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 the t 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 the definition functions of your queryType and mutationType 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.:

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 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 behaviour 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 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.findOne({
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 behaviour 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 server

Finally, you can stop running your Prisma server.

Edit this page on Github