Overview

This upgrade guide describes how to migrate 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 migrated to the new Nexus Framework and the nexus-schema-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.0 CLI
  • created your Prisma 2.0 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:

1.
2├── README.md
3├── package.json
4├── prisma
5│ └── schema.prisma
6├── prisma1
7│ ├── datamodel.prisma
8│ └── prisma.yml
9└── src
10 ├── generated
11 │ ├── nexus-prisma
12 │ ├── nexus.ts
13 │ ├── prisma-client
14 │ └── schema.graphql
15 └── index.ts

The important parts are:

  • A folder called with prisma with your Prisma 2.0 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 the Nexus Framework

To get started, you can remove the old Nexus and Prisma 1 dependencies:

1npm uninstall nexus nexus-prisma graphql prisma-client-lib

Then, you can install the new Nexus dependency in your project:

1npm install nexus

Next, install the 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):

1npm 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:

1npm uninstall @prisma/cli @prisma/client

Note however that you can still invoke the Prisma 2.0 CLI with the familiar command:

1npx 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 Nexus configuration in your code

2.1. Remove makePrismaSchema

With the new Nexus Framework, you don't need to create your GraphQLSchema instance manually any more as was done with makeSchema or makePrismaSchema before. Instead, Nexus now hands you a schema object that can directly be configured by calling functions on it. You'll see how that's done once we start migrating the actual GraphQL types.

However your code calling makeSchema or makePrismaSchema before looks like, you can now just delete it, e.g:

- const schema = makePrismaSchema({
- types: [Query, Mutation, User, Post],
- prisma: {
- datamodelInfo,
- client: prisma,
- },
- outputs: {
- schema: path.join(__dirname, './generated/schema.graphql'),
- typegen: path.join(__dirname, './generated/nexus.ts'),
- },
- nonNullDefaults: {
- input: false,
- output: false,
- },
- typegenAutoConfig: {
- sources: [
- {
- source: path.join(__dirname, './types.ts'),
- alias: 'types',
- },
- ],
- contextType: 'types.Context',
- },
- })

If you want to configure the schema further, you can import the settings object from nexus, e.g.:

1import { settings } from 'nexus'
2
3settings.change({
4 logger: {
5 pretty: true
6 },
7 server: {
8 startMessage: (info) => {
9 settings.original.server.startMessage(info)
10 },
11 },
12 schema: {
13 generateGraphQLSDLFile: './src/generated/schema.graphql'
14 }
15})

2.2. Remove your GraphQL server

The new Nexus Framework comes with an integrated HTTP server based on express-graphql. This means you don't need your previous server (e.g. graphql-yoga or apollo-server) any more.

1npm uninstall graphql-yoga

Once removed, you can also delete the GraphQLServer setup code, e.g.:

- const server = new GraphQLServer({
- schema,
- context: { prisma },
- })
- server.start()

If you need to configure the Express server in specific ways, you can do so by importing the server object from nexus, e.g.:

1import cors from 'cors'
2import { server } from 'nexus'
3
4server.express.use(cors())

2.3. Adjust the context configuration

The context object in GraphQL servers allows your resolvers to communicate with each other. Instead of creating this object manually and passing it to your GraphQL server, you can now call the addToContext function on your main schema object:

1import { schema } from 'nexus'
2
3schema.addToContext(req => {
4 return { ... }
5})

Note: Since you're using the new nexus-plugin-prisma, you don't need to to attach a PrismaClient instance to the context manually any more. The plugin will automatically add it for you as an object called db, so you can access it inside your resolvers e.g. via context.db.user.findMany().

2.4. Use the nexus-plugin-prisma

The new nexus-plugin-prisma package is the equivalent of the nexus-prisma package that was used with Prisma 1.

In your new setup, you now need to integrate the plugin using Nexus' use function. You'll likely want to put that code somewhere at the "root" of your application structure, e.g. in index.ts or something similar.

1import { use } from 'nexus'
2import { prisma } from 'nexus-plugin-prisma'
3
4use(
5 prisma({
6 migrations: false,
7 features: {
8 crud: true,
9 },
10 }),
11)

Note that when adding the nexus-plugin-prisma, you're disabling Prisma Migrate which is currently still in an experimental state and therefore is not recommended for production use. This makes Nexus compatible with the introspection-based workflows.

Next, you can adjust the scripts section inside your package.json to include the following commands:

1{
2 "scripts": {
+ "dev": "nexus dev",
+ "build": "nexus build",
+ "start": "node node_modules/.build"
6 }
7}

The dev script starts a development server that you always should have running in the background when developing your app. Note however that running it right now likely produces TypeScript errors because oyu e.g. uninstalled the nexus-prisma package which is probably still used throughout your application.

As a next step, you'll need to start migrating Nexus' GraphQL type definitions to the new API.

2.5. Configure tsconfig.json

For the new Nexus Framework, be sure that your tsconfig.json has the following properties set:

  • Please add "types.d.ts" to your "include" array.
  • Please add "src" to your "include" array.
  • Please set compilerOptions.rootDir to "src"
  • Please set compilerOptions.noEmit to true. This will ensure you do not accidentally emit using $ tsc. Use $ nexus build to build your app and emit JavaScript.

2. Migrate your GraphQL types

Here's a quick overview of the main differences between the two approaches of creating GraphQL types with the "old" and "new" Nexus.

  • prismaObjectType doesn't exist any more, all types are created with Nexus objectType function.
  • Instead of importing objectType, you can now invoke it on your main schema instance as schema.objectType(...).
  • 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.

2.1. Migrating the Post type

Type definition with the "old" Nexus and nexus-prisma

In the sample app, the User type is defined as follows:

1const User = prismaObjectType({
2 name: 'User',
3 definition(t) {
4 t.prismaFields([
5 'id',
6 'name',
7 'email',
8 {
9 name: 'posts',
10 args: [], // remove the arguments from the `posts` field of the `User` type in the Prisma schema
11 },
12 ])
13 },
14})

Type definition the Nexus Framework and the nexus-plugin-prisma

With the new Nexus Framework, you can now access the objectType function on your main schema instance and expose all fields from the Prisma model like so:

1schema.objectType({
2 name: 'User',
3 definition(t) {
4 t.model.id()
5 t.model.name()
6 t.model.email()
7 t.model.posts({
8 pagination: false,
9 ordering: false,
10 filtering: false
11 })
12 t.model.profile()
13 }
14})

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 User model. Therefore, t.model exposes functions that are named after the fields of the User model.

At this point, you'll probably get errors on the relation fields posts and profile, e.g.:

1Warning: Your GraphQL `User` object definition is projecting a field `posts` with `Post` as output type, but `Post` is not defined in your GraphQL Schema

This is because you didn't add the Post and Profile types to the GraphQL schema yet, the errors will go away once you do that!

2.2. Migrating the Post type

Type definition with the "old" Nexus and nexus-prisma

In the sample app, the Post type is defined as follows:

1const Post = prismaObjectType({
2 name: 'Post',
3 definition(t) {
4 t.prismaFields(['*'])
5 },
6})

The asterisk in prismaFields means that all Prisma fields are exposed.

Type definition the Nexus Framework and the nexus-plugin-prisma

With the new Nexus Framework, you need to expose all fields explicitly, so there's no option to just expose everything from a Prisma model.

Therefore, the new definition of Post must explicitly list all its fields:

1schema.objectType({
2 name: 'Post',
3 definition(t) {
4 t.model.id()
5 t.model.title()
6 t.model.content()
7 t.model.published()
8 t.model.author()
9 }
10})

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.

2.3. Migrating the Profile type

Type definition with the "old" Nexus and nexus-prisma

In the sample app, the Profile type is defined as follows:

1const Profile = prismaObjectType({
2 name: 'Profile',
3 definition(t) {
4 t.prismaFields(['*'])
5 },
6})

The asterisk in prismaFields means that all Prisma fields are exposed.

Type definition the Nexus Framework and the nexus-plugin-prisma

With the new Nexus Framework, you need to expose all fields explicitly, so there's no option to just expose everything from a Prisma model.

Therefore, the new definition of Profile must explicitly list all its fields:

1schema.objectType({
2 name: 'Profile',
3 definition(t) {
4 t.model.id()
5 t.model.bio()
6 t.model.user()
7 }
8})

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.

2.4. Migrating the Category type

Type definition with the "old" Nexus and nexus-prisma

In the sample app, the Category type is defined as follows:

1const Category = prismaObjectType({
2 name: 'Category',
3 definition(t) {
4 t.prismaFields(['*'])
5 },
6})

The asterisk in prismaFields means that all Prisma fields are exposed.

Type definition the Nexus Framework and the nexus-plugin-prisma

With the new Nexus Framework, you need to expose all fields explicitly, so there's no option to just expose everything from a Prisma model.

Therefore, the new definition of Category must explicitly list all its fields:

1schema.objectType({
2 name: 'Category',
3 definition(t) {
4 t.model.id()
5 t.model.name()
6 t.model.posts()
7 }
8})

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.

3. 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:

1input UserUniqueInput {
2 id: String
3 email: String
4}
5
6type Query {
7 posts(searchString: String): [Post!]!
8 user(userUniqueInput: UserUniqueInput!): User
9 users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
10}
11
12type Mutation {
13 createUser(data: UserCreateInput!): User!
14 createDraft(title: String!, content: String, authorId: ID!): Post
15 updateBio(userUniqueInput: UserUniqueInput!, bio: String!): User
16 addPostToCategories(postId: String!, categoryIds: [String!]!): Post
17}

3.1. Migrate GraphQL queries

In this section, you'll migrate all GraphQL queries from the "old" to the "new" Nexus.

3.1.1. Define the Query type

The first step to migrate any queries is to define the Query type of your GraphQL API. Once that's done, you can gradually add operations to it.

Similar to the objectType functions you've used in the previous sections, you don't need to import the queryType function any more. Instead, you access it on your main schema object. Otherwise the API of queryType is remaining the same in the "new" Nexus.

1schema.queryType({
2 definition(t) {
3 // your GraphQL queries + resolvers will be defined here
4 }
5})

3.1.2. Migrate the users query

In our sample API, the users query from the sample GraphQL schema is implemented as follows.

1const Query = prismaObjectType({
2 name: 'Query',
3 definition(t) {
4 t.prismaFields(['users'])
5 },
6})

To get the same behaviour with the new Nexus, you need to call the users function on t.crud:

1schema.queryType({
2 definition(t) {
3 t.crud.posts({
4 filtering: true,
5 ordering: true
6 })
7 }
8})

Recall that the crud property is added to t by the nexus-plugin-prisma (using the same mechanism as for t.model).

3.1.3. Migrate the posts(searchString: String): [Post!]! query

In the sample API, the posts query is implemented as follows:

1queryType({
2 definition(t) {
3 t.list.field('posts', {
4 type: 'Post',
5 args: {
6 searchString: stringArg({ nullable: true }),
7 },
8 resolve: (parent, { searchString }, context) => {
9 return context.prisma.posts({
10 where: {
11 OR: [
12 { title_contains: searchString },
13 { content_contains: searchString },
14 ],
15 },
16 })
17 },
18 })
19 }
20})

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.

1queryType({
2 definition(t) {
3 t.list.field('posts', {
4 type: 'Post',
5 args: {
6 searchString: schema.stringArg({ nullable: true }),
7 },
8 resolve: (parent, { searchString }, context) => {
9 return context.db.post({
10 where: {
11 OR: [
12 { title: { contains: searchString } },
13 { content: { contains: searchString } },
14 ],
15 },
16 })
17 },
18 })
19 }
20})

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.

3.1.3. Migrate the user(uniqueInput: UserUniqueInput): User query

In the sample API, the user query is implemented as follows:

1inputObjectType({
2 name: 'UserUniqueInput',
3 definition(t) {
4 t.string('id')
5 t.string('email')
6 },
7})
8
9queryType({
10 definition(t) {
11 t.field('user', {
12 type: 'User',
13 args: {
14 userUniqueInput: schema.arg({
15 type: 'UserUniqueInput',
16 nullable: false,
17 })
18 },
19 resolve: (_, args, context) => {
20 return context.prisma.user({
21 id: args.userUniqueInput?.id,
22 email: args.userUniqueInput?.email
23 })
24 }
25 })
26 }
27})

The things that need to be updated for this query is the to access inputObjectType and arg on your main schema object as well as the call to Prisma since the new Prisma Client API looks a bit different from the one used in Prisma 1.

1schema.inputObjectType({
2 name: 'UserUniqueInput',
3 definition(t) {
4 t.string('id')
5 t.string('email')
6 },
7})
8
9schema.queryType({
10 definition(t) {
11 t.field('user', {
12 type: 'User',
13 args: {
14 userUniqueInput: schema.arg({
15 type: 'UserUniqueInput',
16 nullable: false,
17 })
18 },
19 resolve: (_, args, context) => {
20 return context.db.user.findOne({
21 where: {
22 id: args.userUniqueInput?.id,
23 email: args.userUniqueInput?.email
24 }
25 })
26 }
27 })
28 }
29})

3.2. Migrate GraphQL mutations

In this section, you'll migrate the GraphQL mutations from the sample schema to the "new" Nexus.

3.2.1. Define the Mutation type

The first step to migrate any mutations is to define the Mutation type of your GraphQL API. Once that's done, you can gradually add operations to it.

Similar to the objectType functions you've used in the previous sections, you don't need to import the mutationType function any more. Instead, you access it on your main schema object. Otherwise the API of mutationType is remaining the same in the "new" Nexus.

1schema.mutationType({
2 definition(t) {
3 // your GraphQL mutations + resolvers will be defined here
4 }
5})

3.2.2. Migrate the createUser mutation

In our sample API, the createUser mutation from the sample GraphQL schema is implemented as follows.

1const Mutation = prismaObjectType({
2 name: 'Mutation',
3 definition(t) {
4 t.prismaFields(['createUser'])
5 },
6})

To get the same behaviour with the "new" Nexus, 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):

1schema.queryType({
2 definition(t) {
3 t.crud.createOneUser({
4 alias: 'createUser'
5 })
6 }
7})

Recall that the crud property is added to t by the nexus-plugin-prisma (using the same mechanism as for t.model).

3.2.3. Migrate the createDraft(title: String!, content: String, authorId: String!): Post! query

In the sample app, the createDraft mutation implemented as follows.

1mutationType({
2 definition(t) {
3 t.field('createDraft', {
4 type: 'Post',
5 args: {
6 title: stringArg({ nullable: false }),
7 content: stringArg(),
8 authorId: stringArg({ nullable: false }),
9 },
10 resolve: (_, args, context) => {
11 return context.prisma.createPost({
12 title: args.title,
13 content: args.content,
14 author: {
15 connect: { id: args.authorId }
16 }
17 })
18 }
19 })
20 }
21})

The things that need to be updated for this mutation is the to access stringArg on your main schema object as well as the call to Prisma since the new Prisma Client API looks a bit different from the one used in Prisma 1.

1schema.mutationType({
2 definition(t) {
3 t.field('createDraft', {
4 type: 'Post',
5 args: {
6 title: schema.stringArg({ nullable: false }),
7 content: schema.stringArg(),
8 authorId: schema.stringArg({ nullable: false }),
9 },
10 resolve: (_, args, context) => {
11 return context.db.post.create({
12 data: {
13 title: args.title,
14 content: args.content,
15 author: {
16 connect: { id: args.authorId }
17 }
18 }
19 })
20 }
21 })
22 }
23})

3.2.4. Migrate the updateBio(bio: String, userUniqueInput: UserUniqueInput!): User mutation

In the sample API, the updateBio mutation is defined and implemented as follows.

1mutationType({
2 definition(t) {
3 t.field('updateBio', {
4 type: 'User',
5 args: {
6 userUniqueInput: arg({
7 type: 'UserUniqueInput',
8 nullable: false
9 }),
10 bio: stringArg()
11 },
12 resolve: (_, args, context) => {
13 return context.prisma.updateUser({
14 where: {
15 id: args.userUniqueInput?.id,
16 email: args.userUniqueInput?.email
17 },
18 data: {
19 profile: {
20 create: { bio: args.bio }
21 }
22 }
23 })
24 }
25 })
26 }
27})

The things that need to be updated for this mutation is the to access stringArg on your main schema object as well as the call to Prisma since the new Prisma Client API looks a bit different from the one used in Prisma 1.

1schema.mutationType({
2 definition(t) {
3 t.field('updateBio', {
4 type: 'User',
5 args: {
6 userUniqueInput: schema.arg({
7 type: 'UserUniqueInput',
8 nullable: false
9 }),
10 bio: schema.stringArg()
11 },
12 resolve: (_, args, context) => {
13 return context.db.user.update({
14 where: {
15 id: args.userUniqueInput?.id,
16 email: args.userUniqueInput?.email
17 },
18 data: {
19 profile: {
20 create: { bio: args.bio }
21 }
22 }
23 })
24 }
25 })
26 }
27})

3.2.5. Migrate the addPostToCategories(postId: String!, categoryIds: [String!]!): Post mutation

In the sample API, the addPostToCategories mutation is defined and implemented as follows.

1mutationType({
2 definition(t) {
3 t.field('addPostToCategories', {
4 type: 'Post',
5 args: {
6 postId: stringArg({ nullable: false }),
7 categoryIds: stringArg({
8 list: true,
9 nullable: false
10 }),
11 },
12 resolve: (_, args, context) => {
13 const ids = args.categoryIds.map(id => ({ id }))
14 return context.prisma.updatePost({
15 where: {
16 id: args.postId
17 },
18 data: {
19 categories: { connect: ids }
20 }
21 })
22 }
23 })
24 }
25})

The things that need to be updated for this mutation is the to access stringArg on your main schema object as well as the call to Prisma since the new Prisma Client API looks a bit different from the one used in Prisma 1.

1schema.mutationType({
2 definition(t) {
3 t.field('addPostToCategories', {
4 type: 'Post',
5 args: {
6 postId: schema.stringArg({ nullable: false }),
7 categoryIds: schema.stringArg({
8 list: true,
9 nullable: false
10 }),
11 },
12 resolve: (_, args, context) => {
13 const ids = args.categoryIds.map(id => ({ id }))
14 return context.db.post.update({
15 where: {
16 id: args.postId
17 },
18 data: {
19 categories: { connect: ids }
20 }
21 })
22 }
23 })
24 }
25})

4. Cleaning up

4.1. Clean up npm dependencies

You can start by removing npm dependencies that were related to the Prisma 1 setup:

1npm uninstall graphql-yoga prisma1

4.2. Delete unused files

Next, delete the files of your Prisma 1 setup:

1rm prisma1/datamodel.prisma prisma1/prisma.yml

4.3. Stop the Prisma server

Finally, you can stop running your Prisma server.

The next step of the upgrade process is to create your GraphQL types. In this case, your GraphQL types will mirror the Prisma models (as it likely was the case in your prisma-binding setup as well). If a GraphQL type deviates from a Prisma model, you'll be able to easily adjust the exposed GraphQL type accordingly using the Nexus API.

For the purpose of this guide, you'll keep all the code in a single file. However, you can structure the files to your personal preference and import accordingly.

Create a new file called graphql.ts right next to app.ts:

1touch graphql.ts

In Nexus, GraphQL types are defined via the objectType function that's exposed by the main schema object. Import schema and then start with the skeleton for your first GraphQL type. In this case, we're starting by mapping Prisma's User model to GraphQL:

1schema.objectType({
2 name: 'User',
3 definition(t) {
4 // the fields of the type will be defined here
5 },
6})

With this code in place, you can start exposing the fields of the User model one by one. You can use your editor's autocompletion to save some typing. Inside the body of the definition function, type t.model. and then hit CTRL+SPACE. This will bring up the autocompletion and suggest all fields that are defined on the User model:

Note that the model property on t is provided by the nexus-plugin-prisma. It leverages the type information from your Prisma schema and allows you to easily expose them via GraphQL.

In that manner, you can start completing your object type definition until you exposed all the fields of the model:

1schema.objectType({
2 name: 'User',
3 definition(t) {
4 t.model.id()
5 t.model.email()
6 t.model.name()
7 t.model.jsonData()
8 t.model.role()
9 t.model.profile()
10 t.model.posts()
11 },
12})

At this point, any relation fields might give you TypeScript errors (in this case, that would be profile and posts which both point to other object types). That's expected, these errors will resolve automatically after you've added the remaining types.

Note: Be sure to have your Nexus development server that you started with npm run dev running all the time. It constantly updates the generated Nexus types that enable the autocompletion in the background as you save a file.

Note that the t.model.posts relation exposes a list of Post objects. By default, Nexus exposes only pagination properties for that list – if you want to add ordering and filtering for that relation as well, you'll need to explicitly enable those:

1schema.objectType({
2 name: 'User',
3 definition(t) {
4 t.model.id()
5 t.model.email()
6 t.model.name()
7 t.model.jsonData()
8 t.model.role()
9 t.model.profile()
+ t.model.posts({
+ filtering: true,
+ ordering: true,
+ })
14 },
15})

Note: If you have one or more fields of type Json on any of your Prisma models, enabling filtering currently produces an error. As a workaround, you can temporarily comment out these field(s) in your Prisma schema.

Once you're done with the first type, you can start defining the remaining ones.

To expose all sample Prisma models with Nexus, the following code is needed:

1schema.objectType({
2 name: 'User',
3 definition(t) {
4 t.model.id()
5 t.model.email()
6 t.model.name()
7 t.model.jsonData()
8 t.model.role()
9 t.model.profile()
10 t.model.posts({
11 filtering: true,
12 ordering: true,
13 })
14 },
15})
16
17schema.objectType({
18 name: 'Post',
19 definition(t) {
20 t.model.id()
21 t.model.createdAt()
22 t.model.updatedAt()
23 t.model.title()
24 t.model.content()
25 t.model.published()
26 t.model.author()
27 t.model.authorId()
28 t.model.categories({
29 filtering: true,
30 ordering: true,
31 })
32 },
33})
34
35schema.objectType({
36 name: 'Profile',
37 definition(t) {
38 t.model.id()
39 t.model.bio()
40 t.model.user()
41 t.model.userId()
42 },
43})
44
45schema.objectType({
46 name: 'Category',
47 definition(t) {
48 t.model.id()
49 t.model.name()
50 t.model.posts({
51 filtering: true,
52 ordering: true,
53 })
54 },
55})

You can view the current version of your GraphQL schema in SDL in the generated GraphQL schema file in ./src/generated/nexus.graphql.

3. 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 that's built with Nexus.

For this guide, the following sample GraphQL schema will be used:

1# import Post from './generated/prisma.graphql'
2# import User from './generated/prisma.graphql'
3# import Category from './generated/prisma.graphql'
4
5input UserUniqueInput {
6 id: String
7 email: String
8}
9
10type Query {
11 posts(searchString: String): [Post!]!
12 user(userUniqueInput: UserUniqueInput!): User
13 users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
14}
15
16type Mutation {
17 createUser(data: UserCreateInput!): User!
18 createDraft(title: String!, content: String, authorId: ID!): Post
19 updateBio(userUniqueInput: UserUniqueInput!, bio: String!): User
20 addPostToCategories(postId: String!, categoryIds: [String!]!): Post
21}

3.1. Migrate GraphQL queries

In this section, you'll migrate all GraphQL queries from prisma-binding to Nexus.

3.1.1. Define the Query type

The first step to migrate any queries is to define the Query type of your GraphQL API. Once that's done, you can gradually add operations to it. Add the following definition to graphql.ts:

1schema.queryType({
2 definition(t) {
3 // your GraphQL queries + resolvers will be defined here
4 }
5})

Note that the Nexus API can seem a bit confusing at first. The code above is actually equivalent to writing the following:

1schema.queryType({
2 definition: t => {
3 // your GraphQL queries + resolvers will be defined here
4 }
5})

However, the first version of the two is the common conventions for Nexus, so it'll be used throughout this guide.

3.1.2. Migrate the users query (which uses forwardTo)

In our sample API, the users query from the sample GraphQL schema is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Query {
2 users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
3 # ... other queries
4}
Resolver implementation with prisma-binding
1const resolvers = {
2 Query: {
3 users: forwardTo('prisma'),
4 // ... other resolvers
5 }
6}

To mirror the same behaviour with Nexus, you can use the crud property on the t variable inside the definition function.

Similar to model, this property is available because you're using the nexus-prisma-plugin which leverages type information from your Prisma models and auto-generates resolvers under the hood. The crud property also supports autocompletion, so you can explore all available queries in your editor again:

Forwarding the query with the nexus-prisma-plugin

To add the users query to your GraphQL API, add the following lines to the query type definition:

1schema.queryType({
2 definition(t) {
+ t.crud.users({
+ filtering: true,
+ ordering: true,
+ })
7 }
8})

If you have the Nexus development server running, you can save the file and your GraphQL API will be updated to expose the new users query. You can also observe this by looking at the Query type inside the generated nexus.graphql file:

1type Query {
2 users(after: UserWhereUniqueInput, before: UserWhereUniqueInput, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]!
3}

You can now write your first query against the new API, e.g.:

1{
2 users {
3 id
4 name
5 profile {
6 id
7 bio
8 }
9 posts {
10 id
11 title
12 categories {
13 id
14 name
15 }
16 }
17 }
18}

If your application exposes all CRUD operations from Prisma using forwardTo, you can now continue adding all remaining ones using the same approach via t.crud. To learn how "custom" queries can be defined and resolved using Nexus, move on to the next sections.

3.1.3. Migrate the posts(searchString: String): [Post!]! query

The posts query is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Query {
2 posts(searchString: String): [Post!]!
3 # ... other queries
4}
Resolver implementation with prisma-binding
1const resolvers = {
2 Query: {
3 posts: (_, args, context, info) => {
4 return context.prisma.query.posts(
5 {
6 where: {
7 OR: [
8 { title_contains: args.searchString },
9 { content_contains: args.searchString },
10 ],
11 },
12 },
13 info,
14 )
15 },
16 // ... other resolvers
17 }
18}
Code-first schema definition with nexus

To get the same behaviour with Nexus, you'll need to add a t.field definition to the queryType:

1schema.queryType({
2 definition(t) {
3 // ... previous queries
4
+ t.list.field('posts', {
+ type: 'Post',
+ nullable: false,
+ args: { searchString: schema.stringArg() }
+ })
10 }
11})

If you look at the generated SDL version of your GraphQL schema inside nexus.graphql, you'll notice that this has added the correct definition to your GraphQL schema already:

1type Query {
2 posts(searchString: String): [Post!]!
3 users(after: UserWhereUniqueInput, before: UserWhereUniqueInput, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]!
4}

You can even send the respective query via the GraphQL Playground already:

1{
2 posts {
3 id
4 title
5 }
6}

However, the response of such a query will always be:

1{
2 "data": {
3 "posts": null
4 }
5}
Resolver implementation with nexus

That's because you're still missing the resolver implementation for that query. You can add the resolver with Nexus as follows:

1schema.queryType({
2 definition(t) {
3 // ... previous queries
4
5 t.list.field('posts', {
6 type: 'Post',
7 nullable: false,
8 args: { searchString: schema.stringArg() },
+ resolve: (_, args, context) => {
+ return context.db.post.findMany({
+ where: {
+ OR: [{
+ title: { contains: args.searchString }
+ }, {
+ content: { contains: args.searchString }
+ }]
+ }
+ })
+ }
20 })
21 }
22})

If you're re-sending the same query from before, you'll find that it now returns actual data instead of null.

Note that the db object that's attached to the context argument represents your PrismaClient instance that you can use to send queries to your database.

3.1.3. Migrate the user(uniqueInput: UserUniqueInput): User query

In our sample app, the user query is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Query {
2 user(userUniqueInput: UserUniqueInput): User
3 # ... other queries
4}
5
6input UserUniqueInput {
7 id: String
8 email: String
9}

Note that this is a bit of a contrived example to demonstrate usage of input types with Nexus.

Resolver implementation with prisma-binding
1const resolvers = {
2 Query: {
3 user: (_, args, context, info) => {
4 return context.prisma.query.user(
5 {
6 where: args.userUniqueInput,
7 },
8 info,
9 )
10 },
11 // ... other resolvers
12 }
13}
Code-first schema definition with nexus

To get the same behaviour with Nexus, you'll need to add a t.field definition to the queryType and define an inputObjectType that includes the two @unique fields of your User model:

+schema.inputObjectType({
+ name: 'UserUniqueInput',
+ definition(t) {
+ t.string('id')
+ t.string('email')
+ },
+})
8
9schema.queryType({
10 definition(t) {
11 // ... previous queries
12
+ t.field('user', {
+ type: 'User',
+ args: {
+ userUniqueInput: schema.arg({
+ type: 'UserUniqueInput',
+ nullable: false,
+ })
+ }
+ })
22 }
23})

If you look at the generated SDL version of your GraphQL schema inside nexus.graphql, you'll notice that this change already added the correct definition to your GraphQL schema:

1type Query {
2 posts(searchString: String): [Post!]
3 user(userUniqueInput: UserUniqueInput!): User
4 users(after: UserWhereUniqueInput, before: UserWhereUniqueInput, first: Int, last: Int, orderBy: UserOrderByInput, skip: Int, where: UserWhereInput): [User!]!
5}
6
7 input UserUniqueInput {
8 email: String
9 id: String
10 }

You can even send the respective query via the GraphQL Playground already:

1{
2 user(userUniqueInput: { email: "alice@prisma.io" }) {
3 id
4 name
5 }
6}

However, the response of such a query will always be:

1{
2 "data": {
3 "user": null
4 }
5}
Code-first resolver implementation with nexus

That's because you're still missing the resolver implementation for that query. You can add the resolver with Nexus as follows:

1schema.inputObjectType({
2 name: 'UserUniqueInput',
3 definition(t) {
4 t.string('id')
5 t.string('email')
6 },
7})
8
9schema.queryType({
10 definition(t) {
11 // ... previous queries
12
13 t.field('user', {
14 type: 'User',
15 nullable: true,
16 args: {
17 userUniqueInput: schema.arg({
18 type: 'UserUniqueInput',
19 nullable: false,
20 })
21 },
+ resolve: (_, args, context) => {
+ return context.db.user.findOne({
+ where: {
+ id: args.userUniqueInput?.id,
+ email: args.userUniqueInput?.email
+ }
+ })
+ }
30 })
31 },
32 }
33})

If you're re-sending the same query from before, you'll find that it now returns actual data instead of null.

3.1. Migrate GraphQL mutations

In this section, you'll migrate the GraphQL mutations from the sample schema to the Nexus.

3.1.1. Define the Mutation type

The first step to migrate any mutations is to define the Mutation type of your GraphQL API. Once that's done, you can gradually add operations to it. Add the following definition to graphql.ts:

1schema.mutationType({
2 definition(t) {
3 // your GraphQL mutations + resolvers will be defined here
4 }
5})

3.1.2. Migrate the createUser mutation (which uses forwardTo)

In the sample app, the createUser mutation from the sample GraphQL schema is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Mutation {
2 createUser(data: UserCreateInput!): User!
3 # ... other mutations
4}
Resolver implementation with prisma-binding
1const resolvers = {
2 Mutation: {
3 createUser: forwardTo('prisma'),
4 // ... other resolvers
5 }
6}

Similar to forwarding GraphQL queries, you can use the crud property on the t variable inside the definition function in order to expose full CRUD capabilities for Prisma models.

Similar to model, this property is available because you're using the nexus-prisma-plugin which leverages type information from your Prisma models and auto-generates resolvers under the hood. The crud property supports autocompletion when defining mutations as well, so you can explore all available operations in your editor again:

Forwarding the mutation with the nexus-prisma-plugin

To add the createUser mutation to your GraphQL API, add the following lines to the query type definition:

1schema.mutationType({
2 definition(t) {
+ t.crud.createOneUser({
+ alias: 'createUser',
+ })
6 }
7})

Note that the default name for the mutation in your GraphQL schema is createdOneUser (named after the function which is exposed by t.crud). In order to rename it to createUser, you need to provide the alias property.

If you have the Nexus development server running, you can save the file and your GraphQL API will be updated to expose the new createUser mutation. You can also observe this by looking at the Mutation type inside the generated nexus.graphql file:

1type Mutation {
2 createUser(data: UserCreateInput!): User!
3}

You can now write your first mutation against the new API, e.g.:

1mutation {
2 createUser(data: {
3 name: "Alice",
4 email: "alice@prisma.io"
5 }) {
6 id
7 }
8}

If your application exposes all CRUD operations from Prisma using forwardTo, you can now continue adding all remaining ones using the same approach via t.crud. To learn how "custom" mutations can be defined and resolved using Nexus, move on to the next sections.

3.1.3. Migrate the createDraft(title: String!, content: String, authorId: String!): Post! query

In the sample app, the createDraft mutation is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Mutation {
2 createDraft(title: String!, content: String, authorId: String!): Post!
3 # ... other mutations
4}
Resolver implementation with prisma-binding
1const resolvers = {
2 Mutation: {
3 createDraft: (_, args, context, info) => {
4 return context.prisma.mutation.createPost(
5 {
6 data: {
7 title: args.title,
8 content: args.content,
9 author: {
10 connect: {
11 id: args.authorId,
12 },
13 },
14 },
15 },
16 info,
17 )
18 },
19 // ... other resolvers
20 }
21}
Code-first schema definition with nexus

To get the same behaviour with Nexus, you'll need to add a t.field definition to the mutationType:

1schema.mutationType({
2 definition(t) {
3 // ... previous mutations
4
+ t.field('createDraft', {
+ type: 'Post',
+ args: {
+ title: schema.stringArg({ nullable: false }),
+ content: schema.stringArg(),
+ authorId: schema.stringArg({ nullable: false }),
+ }
+ })
13 }
14})

If you look at the generated SDL version of your GraphQL schema inside nexus.graphql, you'll notice that this has added the correct definition to your GraphQL schema already:

1type Mutation {
2 createUser(data: UserCreateInput!): User!
3 createDraft(title: String!, content: String, authorId: String!): Post!
4}

You can even send the respective mutation via the GraphQL Playground already:

1mutation {
2 createDraft(
3 title: "Hello World",
4 authorId: "__AUTHOR_ID__"
5 ) {
6 id
7 published
8 auhor {
9 id
10 name
11 }
12 }
13}

However, the response of such a mutation will always be:

1{
2 "data": {
3 "createDraft": null
4 }
5}
Resolver implementation with nexus

That's because you're still missing the resolver implementation for that mutation. You can add the resolver with Nexus as follows:

1schema.mutationType({
2 definition(t) {
3 // ... previous mutations
4
5 t.field('createDraft', {
6 type: 'Post',
7 args: {
8 title: schema.stringArg({ nullable: false }),
9 content: schema.stringArg(),
10 authorId: schema.stringArg({ nullable: false }),
11 },
+ resolve: (_, args) => {
+ return context.db.post.create({
+ data: {
+ title: args.title,
+ content: args.content,
+ author: {
+ connect: { id: args.authorId }
+ }
+ }
+ })
+ }
23 })
24 }
25})

If you're re-sending the same query from before, you'll find that it now returns actual data instead of null.

3.1.4. Migrate the updateBio(bio: String, userUniqueInput: UserUniqueInput!): User mutation

In the sample app, the updateBio mutation is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Mutation {
2 updateBio(bio: String!, userUniqueInput: UserUniqueInput!): User
3 # ... other mutations
4}
Resolver implementation with prisma-binding
1const resolvers = {
2 Mutation: {
3 updateBio: (_, args, context, info) => {
4 return context.prisma.mutation.updateUser(
5 {
6 data: {
7 profile: {
8 update: { bio: args.bio }
9 }
10 },
11 where: { id: args.userId }
12 },
13 info,
14 )
15 },
16 // ... other resolvers
17 }
18}
Code-first schema definition with nexus

To get the same behaviour with Nexus, you'll need to add a t.field definition to the mutationType:

1schema.mutationType({
2 definition(t) {
3 // ... previous mutations
4
+ t.field('updateBio', {
+ type: 'User',
+ args: {
+ userUniqueInput: schema.arg({
+ type: 'UserUniqueInput',
+ nullable: false
+ }),
+ bio: schema.stringArg({ nullable: false })
+ },
+ })
15 }
16})

If you look at the generated SDL version of your GraphQL schema inside nexus.graphql, you'll notice that this has added the correct definition to your GraphQL schema already:

1type Mutation {
2 createUser(data: UserCreateInput!): User!
3 createDraft(title: String!, content: String, authorId: String!): Post!
4 updateBio(bio: String!, userUniqueInput: UserUniqueInput!): User
5}

You can even send the respective mutation via the GraphQL Playground already:

1mutation {
2 updateBio(
3 userUniqueInput: {
4 email: "alice@prisma.io"
5 }
6 bio: "I like turtles"
7 ){
8 id
9 name
10 profile {
11 id
12 bio
13 }
14 }
15}

However, the response of such a mutation will always be:

1{
2 "data": {
3 "updateBio": null
4 }
5}
Resolver implementation with nexus

That's because you're still missing the resolver implementation for that query. You can add the resolver with Nexus as follows:

1schema.mutationType({
2 definition(t) {
3 // ... previous mutations
4
5 t.field('updateBio', {
6 type: 'User',
7 args: {
8 userUniqueInput: schema.arg({
9 type: 'UserUniqueInput',
10 nullable: false
11 }),
12 bio: schema.stringArg()
13 },
+ resolve: (_, args, context) => {
+ return context.db.user.update({
+ where: {
+ id: args.userUniqueInput?.id,
+ email: args.userUniqueInput?.email
+ },
+ data: {
+ profile: {
+ create: { bio: args.bio }
+ }
+ }
+ })
+ }
+ })
28 }
29})

If you're re-sending the same query from before, you'll find that it now returns actual data instead of null.

3.1.5. Migrate the addPostToCategories(postId: String!, categoryIds: [String!]!): Post mutation

In our sample app, the addPostToCategories mutation is defined and implemented as follows.

SDL schema definition with prisma-binding
1type Mutation {
2 addPostToCategories(postId: String!, categoryIds: [String!]!): Post
3 # ... other mutations
4}
Resolver implementation with prisma-binding
1const resolvers = {
2 Mutation: {
3 addPostToCategories: (_, args, context, info) => {
4 const ids = args.categoryIds.map(id => ({ id }))
5 return context.prisma.mutation.updatePost(
6 {
7 data: {
8 categories: {
9 connect: ids
10 }
11 },
12 where: {
13 id: args.postId
14 }
15 },
16 info,
17 )
18 },
19 // ... other resolvers
20 }
21}
Code-first schema definition with nexus

To get the same behaviour with Nexus, you'll need to add a t.field definition to the mutationType:

1schema.mutationType({
2 definition(t) {
3 // ... mutations from before
4
+ t.field('addPostToCategories', {
+ type: 'Post',
+ args: {
+ postId: schema.stringArg({ nullable: false }),
+ categoryIds: schema.stringArg({
+ list: true,
+ nullable: false
+ }),
+ },
+ })
15 }
16})

If you look at the generated SDL version of your GraphQL schema inside nexus.graphql, you'll notice that this has added the correct definition to your GraphQL schema already:

1type Mutation {
2 createUser(data: UserCreateInput!): User!
3 createDraft(title: String!, content: String, authorId: String!): Post!
4 updateBio(bio: String, userUniqueInput: UserUniqueInput!): User
5 addPostToCategories(postId: String!, categoryIds: [String!]!): Post
6}

You can even send the respective query via the GraphQL Playground already:

1mutation {
2 addPostToCategories(
3 postId: "__AUTHOR_ID__"
4 categoryIds: [
5 "__CATEGORY_ID_1__",
6 "__CATEGORY_ID_2__",
7 ]
8 ) {
9 id
10 title
11 categories {
12 id
13 name
14 }
15 }
16}

However, the response of such a mutation will always be:

1{
2 "data": {
3 "addPostToCategories": null
4 }
5}
Resolver implementation with nexus

That's because you're still missing the resolver implementation for that query. You can add the resolver with Nexus as follows:

1schema.mutationType({
2 definition(t) {
3 // ... mutations from before
4 t.field('addPostToCategories', {
5 type: 'Post',
6 args: {
7 postId: schema.stringArg({ nullable: false }),
8 categoryIds: schema.stringArg({
9 list: true,
10 nullable: false
11 }),
12 },
+ resolve: (_, args, context) => {
+ const ids = args.categoryIds.map(id => ({ id }))
+ return context.db.post.update({
+ where: {
+ id: args.postId
+ },
+ data: {
+ categories: { connect: ids }
+ }
+ })
+ }
24 })
25 }
26})

If you're re-sending the same query from before, you'll find that it now returns actual data instead of null.

4. Cleaning up

Since the entire app has now been upgrade to Prisma 2.0 and Nexus, you can delete all unnecessary files and remove the no longer needed dependencies.

4.1. Clean up npm dependencies

You can start by removing npm dependencies that were related to the Prisma 1 setup:

1npm uninstall graphql-cli graphql-yoga prisma-binding prisma1

4.2. Delete unused files

Next, delete the files of your Prisma 1 setup:

1rm prisma1/datamodel.prisma prisma1/prisma.yml

You can also delete any remaining .js files, the schema.graphql and prisma.graphql files.

4.3. Stop the Prisma server

Finally, you can stop running your Prisma server.

Edit this page on Github