Overview

This upgrade guide describes how to migrate a Node.js project that's based on Prisma 1 and uses prisma-binding to implement a GraphQL server.

The code will keep the SDL-first approach for constructing the GraphQL schema. When migrating from prisma-binding to Prisma Client, the main difference is that the info object can't be used to resolve relations automatically any more, instead you'll need to implement your type resolvers to ensure that relations get resolved properly.

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:

.
├── README.md
├── package.json
├── prisma
│ └── schema.prisma
├── prisma1
│ ├── datamodel.prisma
│ └── prisma.yml
└── src
├── generated
│ └── prisma.graphql
├── index.js
└── schema.graphql

The important parts are:

  • A folder called with prisma with your Prisma 2.0 schema
  • A folder called src with your application code and a schema called schema.graphql

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. Adjusting your GraphQL schema

With prisma-binding, your approach for defining your GraphQL schema (sometimes called application schema) is based on importing GraphQL types from the generated prisma.graphql file (in Prisma 1, this is typically called Prisma schema). These types mirror the types from your Prisma 1 datamodel and serve as foundation for your GraphQL API.

With Prisma 2.0, there's no prisma.graphql file any more that you could import from. Therefore, you have to spell out all the types of your GraphQL schema directly inside your schema.graphql file.

The easiest way to do so is by downloading the full GraphQL schema from the GraphQL Playground. To do so, open the SCHEMA tab and click the DOWNLOAD button in the top-right corner, then select SDL:

Alternatively, you can use the get-schema command of the GraphQL CLI to download your full schema:

graphql get-schema --endpoint __GRAPHQL_YOGA_ENDPOINT__ --output schema.graphql --no-all

Note: With the above command, you need to replace the __GRAPHQL_YOGA_ENDPOINT__ placeholder with the actual endpoint of your GraphQL Yoga server.

Once you obtained the schema.graphql file, replace your current version in src/schema.graphql with the new contents. Note that the two schemas are 100% equivalent, except that the new one doesn't use graphql-import for importing types from a different file. Instead, it spells out all types in a single file.

Here's a comparison of these two versions of the sample GraphQL schema that we'll migrate in this guide (you can use the tabs to switch between the two versions):

Before (with grahpql-import)
After (used with Prisma 2.0)
# import Post from './generated/prisma.graphql'
# import User from './generated/prisma.graphql'
# import Category from './generated/prisma.graphql'
type Query {
posts(searchString: String): [Post!]!
user(userUniqueInput: UserUniqueInput!): User
users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
allCategories: [Category!]!
}
input UserUniqueInput {
id: String
email: String
}
type Mutation {
createDraft(authorId: ID!, title: String!, content: String!): Post
publish(id: ID!): Post
deletePost(id: ID!): Post
signup(name: String!, email: String!): User!
updateBio(userId: String!, bio: String!): User
addPostToCategories(postId: String!, categoryIds: [String!]!): Post
}

You'll notice that the new version of your GraphQL schema not only defines the models that were imported directly, but also additional types (e.g. input types) that were not present in the schema before.

2. Set up your PrismaClient instance

PrismaClient is your new interface to the database in Prisma 2.0. It lets you invoke various methods which build SQL queries and send them to the database, returning the results as plain JavaScript objects.

The PrismaClient query API is inspired by the initial prisma-binding API, so a lot of the queries you send with Prisma Client will feel familiar.

Similar to the prisma-binding instance from Prisma 1, you also want to attach your PrismaClient from Prisma 2.0 to GraphQL's context so that in can be accessed inside your resolvers:

1const server = new GraphQLServer({
2 typeDefs: 'src/schema.graphql',
3 resolvers,
4 context: req => ({
5 ...req,
- prisma: new Prisma({
- typeDefs: 'src/generated/prisma.graphql',
- endpoint: 'http://localhost:4466',
- }),
+ prisma: new PrismaClient()
11 }),
12})

In the code block above, the red lines are the lines to be removed from your current setup, the green lines are the ones that you should add. Of course, it's possible that your previous setup differed from this one (e.g. it's unlikely that your Prisma endpoint was http://localhost:4466 if you're running your API in production), this is just a sample to indicate what it could look like.

When you're now accessing context.prisma inside of a resolver, you now have access to the Prisma Client queries.

2. Write your GraphQL type resolvers

prisma-binding was able to magically resolve relations in your GraphQL schema. When not using prisma-binding though, you need to explicitly resolve your relations using so-called type resolvers.

Note You can learn more about the concept of type resolvers and why they're necessary in this article: GraphQL Server Basics: GraphQL Schemas, TypeDefs & Resolvers Explained

2.1. Implementing the type resolver for the User type

The new User type in our sample GraphQL schema is defined as follows:

type User implements Node {
id: ID!
email: String
name: String!
posts(
where: PostWhereInput
orderBy: PostOrderByInput
skip: Int
after: String
before: String
first: Int
last: Int
): [Post!]
role: Role!
profile: Profile
jsonData: Json
}

This type has two relations:

  • The posts field denotes a 1-n relation to Post
  • The profile field denotes a 1-1 relation to Profile

Since you're not using prisma-binding any more, you now need to resolve these relations "manually" in type resolvers.

You can do so by adding a User field to your resolver map and implement the resolvers for the posts and profile relations as follows:

1const resolvers = {
2 Query: {
3 // ... your query resolvers
4 },
5 Mutation: {
6 // ... your mutation resolvers
7 },
+ User: {
+ posts: (parent, args, context) => {
+ return context.prisma.posts.findOne({
+ where: {
+ author: { id: parent.id }
+ }
+ })
+ },
+ profile: (parent, args, context) => {
+ return context.prisma.user.findOne({
+ where: { id: parent.id }
+ }).profile()
+ }
+ },
22}

Inside of these resolvers, you're using your new PrismaClient to perform a query against the database. Inside the posts resolver, the database query loads all Post records from the specified author (whose id is carried in the parent object). Inside the profile resolver, the database query loads the Profile record from the specified user (whose id is carried in the parent object).

Thanks to these extra resolvers, you'll now be able to nest relations in your GraphQL queries/mutations whenever you're requesting information about the User type in a query, e.g.:

{
users {
id
name
posts { # fetching this relation is enabled by the new type resolver
id
title
}
profile { # fetching this relation is enabled by the new type resolver
id
bio
}
}
}

2.2. Implementing the type resolver for the Post type

The new Post type in our sample GraphQL schema is defined as follows:

type Post implements Node {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
title: String!
content: String
published: Boolean!
author: User
categories(
where: CategoryWhereInput
orderBy: CategoryOrderByInput
skip: Int
after: String
before: String
first: Int
last: Int
): [Category!]
}

This type has two relations:

  • The author field denotes a 1-n relation to User
  • The categories field denotes a m-n relation to Category

Since you're not using prisma-binding any more, you now need to resolve these relations "manually" in type resolvers.

You can do so by adding a Post field to your resolver map and implement the resolvers for the author and categories relations as follows:

1const resolvers = {
2 Query: {
3 // ... your query resolvers
4 },
5 Mutation: {
6 // ... your mutation resolvers
7 },
8 User: {
9 // ... your type resolvers for `User` from before
10 },
+ Post: {
+ author: (parent, args, context) => {
+ return context.prisma.post.findOne({
+ where: { id: parent.id }
+ }).author()
+ },
+ categories: (parent, args, context) => {
+ return context.prisma.post.findOne({
+ where: { id: parent.id }
+ }).categories()
+ }
+ },
23}

Inside of these resolvers, you're using your new PrismaClient to perform a query against the database. Inside the author resolver, the database query loads the User record that represents the author of the Post. Inside the categories resolver, the database query loads all Category records from the specified post (whose id is carried in the parent object).

Thanks to these extra resolvers, you'll now be able to nest relations in your GraphQL queries/mutations whenever you're requesting information about the User type in a query, e.g.:

{
posts {
id
title
author { # fetching this relation is enabled by the new type resolver
id
name
}
categories { # fetching this relation is enabled by the new type resolver
id
name
}
}
}

2.3. Implementing the type resolver for the Profile type

The new Profile type in our sample GraphQL schema is defined as follows:

type Profile implements Node {
id: ID!
bio: String
user: User!
}

This type has one relation: The user field denotes a 1-n relation to User

Since you're not using prisma-binding any more, you now need to resolve this relation "manually" in type resolvers.

You can do so by adding a Profile field to your resolver map and implement the resolvers for the posts and profile relations as follows:

1const resolvers = {
2 Query: {
3 // ... your query resolvers
4 },
5 Mutation: {
6 // ... your mutation resolvers
7 },
8 User: {
9 // ... your type resolvers for `User` from before
10 },
11 Post: {
12 // ... your type resolvers for `Post` from before
13 },
+ Profile: {
+ user: (parent, args, context) => {
+ return context.prisma.user.findOne({
+ where: {
+ profile: { id: parent.id }
+ }
+ })
+ }
+ }
23}

Inside of this resolver, you're using your new PrismaClient to perform a query against the database. Inside the user resolver, the database query loads the User records from the specified profile (whose id is carried in the parent object).

Thanks to this extra resolver, you'll now be able to nest relations in your GraphQL queries/mutations whenever you're requesting information about the Profile type in a query, e.g.:

{
posts {
id
title
author {
id
name
profile { # fetching this relation is enabled by the new type resolver
id
bio
}
}
}
}

2.4. Implementing the type resolver for the Category type

The new Category type in our sample GraphQL schema is defined as follows:

type Category implements Node {
id: ID!
name: String!
posts(
where: PostWhereInput
orderBy: PostOrderByInput
skip: Int
after: String
before: String
first: Int
last: Int
): [Post!]
}

This type has one relations: The posts field denotes a m-n relation to Post

Since you're not using prisma-binding any more, you now need to resolve these relations "manually" in type resolvers.

You can do so by adding a User field to your resolver map and implement the resolvers for the posts and profile relations as follows:

1const resolvers = {
2 Query: {
3 // ... your query resolvers
4 },
5 Mutation: {
6 // ... your mutation resolvers
7 },
8 User: {
9 // ... your type resolvers for `User` from before
10 },
11 Post: {
12 // ... your type resolvers for `Post` from before
13 },
14 Profile: {
15 // ... your type resolvers for `User` from before
16 },
17 Category: {
18 posts: (parent, args, context) => {
19 return context.prisma.findOne({
20 where: { id: parent.id }
21 }).posts()
22 }
23 },
24}

Inside of this resolver, you're using your new PrismaClient to perform a query against the database. Inside the posts resolver, the database query loads all Post records from the specified categories (whose id is carried in the parent object).

Thanks to this extra resolver, you'll now be able to nest relations in your GraphQL queries/mutations whenever you're requesting information about a Category type in a query, e.g.:

{
posts {
id
title
categories { # fetching this relation is enabled by the new type resolver
id
name
}
}
}

With all your type resolvers in place, you can start migrating the actual GraphQL API operations.

3. Migrate GraphQL operations

3.1. Migrate GraphQL queries

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

3.1.1. 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
type Query {
users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
# ... other queries
}
Resolver implementation with prisma-binding
const resolvers = {
Query: {
users: forwardTo('prisma'),
// ... other resolvers
}
}
Implementing the users resolver with Prisma Client

TBD

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
type Query {
posts(searchString: String): [Post!]!
# ... other queries
}
Resolver implementation with prisma-binding
const resolvers = {
Query: {
posts: (_, args, context, info) => {
return context.prisma.query.posts(
{
where: {
OR: [
{ title_contains: args.searchString },
{ content_contains: args.searchString },
],
},
},
info,
)
},
// ... other resolvers
}
}
Implementing the posts resolver with Prisma Client

To get the same behaviour with the new Prisma Client, you'll need to adjust your resolver implementation:

1const resolvers = {
2 Query: {
3 posts: (_, args, context, info) => {
4 return context.prisma.post.findMany({
5 where: {
6 OR: [
7 { title: { contains: args.searchString } },
8 { content: { contains: args.searchString } },
9 ],
10 },
11 })
12 },
13 // ... other resolvers
14 }
15}

You can now send the respective query in the GraphQL Playground:

{
posts {
id
title
author {
id
name
}
}
}

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
type Query {
user(userUniqueInput: UserUniqueInput): User
# ... other queries
}
input UserUniqueInput {
id: String
email: String
}
Resolver implementation with prisma-binding
const resolvers = {
Query: {
user: (_, args, context, info) => {
return context.prisma.query.user(
{
where: args.userUniqueInput,
},
info,
)
},
// ... other resolvers
}
}
Implementing the user resolver with Prisma Client

To get the same behaviour with the new Prisma Client, you'll need to adjust your resolver implementation:

1const resolvers = {
2 Query: {
3 user: (_, args, context, info) => {
4 return context.prisma.user.findOne({
5 where: args.userUniqueInput,
6 })
7 }
8 // ... other resolvers
9 }
10}

You can now send the respective query via the GraphQL Playground:

{
user(userUniqueInput: { email: "alice@prisma.io" }) {
id
name
}
}

3.1. Migrate GraphQL mutations

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

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
type Mutation {
createUser(data: UserCreateInput!): User!
# ... other mutations
}
Resolver implementation with prisma-binding
const resolvers = {
Mutation: {
createUser: forwardTo('prisma'),
// ... other resolvers
}
}
Implementing the createUser resolver with Prisma Client

To get the same behaviour with the new Prisma Client, you'll need to adjust your resolver implementation:

1const resolvers = {
2 Mutation: {
3 createUser: (_, args, context, info) => {
4 return context.prisma.user.create({
5 data: args.data
6 })
7 },
8 // ... other resolvers
9 }
10}

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

mutation {
createUser(data: {
name: "Alice",
email: "alice@prisma.io"
}) {
id
}
}

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
type Mutation {
createDraft(title: String!, content: String, authorId: String!): Post!
# ... other mutations
}
Resolver implementation with prisma-binding
const resolvers = {
Mutation: {
createDraft: (_, args, context, info) => {
return context.prisma.mutation.createPost(
{
data: {
title: args.title,
content: args.content,
author: {
connect: {
id: args.authorId,
},
},
},
},
info,
)
},
// ... other resolvers
}
}
Implementing the createDraft resolver with Prisma Client

To get the same behaviour with the new Prisma Client, you'll need to adjust your resolver implementation:

1const resolvers = {
2 Mutation: {
3 createDraft: (_, args, context, info) => {
4 return context.prisma.post.create({
5 data: {
6 title: args.title,
7 content: args.content,
8 author: {
9 connect: {
10 id: args.authorId,
11 },
12 },
13 },
14 })
15 }
16 // ... other resolvers
17 }
18}

You can now send the respective mutation via the GraphQL Playground:

mutation {
createDraft(
title: "Hello World",
authorId: "__AUTHOR_ID__"
) {
id
published
auhor {
id
name
}
}
}

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
type Mutation {
updateBio(bio: String!, userUniqueInput: UserUniqueInput!): User
# ... other mutations
}
Resolver implementation with prisma-binding
const resolvers = {
Mutation: {
updateBio: (_, args, context, info) => {
return context.prisma.mutation.updateUser(
{
data: {
profile: {
update: { bio: args.bio }
}
},
where: { id: args.userId }
},
info,
)
},
// ... other resolvers
}
}
Implementing the updateBio resolver with Prisma Client

To get the same behaviour with Prisma Client, you'll need to adjust your resolver implementation:

1const resolvers = {
2 Mutation: {
3 updateBio: (_, args, context, info) => {
4 return context.prisma.user.update({
5 data: {
6 profile: {
7 update: { bio: args.bio }
8 }
9 },
10 where: args.userUniqueInput
11 })
12 },
13 // ... other resolvers
14 }
15}

You can now send the respective mutation via the GraphQL Playground :

mutation {
updateBio(
userUniqueInput: {
email: "alice@prisma.io"
}
bio: "I like turtles"
){
id
name
profile {
id
bio
}
}
}

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
type Mutation {
addPostToCategories(postId: String!, categoryIds: [String!]!): Post
# ... other mutations
}
Resolver implementation with prisma-binding
const resolvers = {
Mutation: {
addPostToCategories: (_, args, context, info) => {
const ids = args.categoryIds.map(id => ({ id }))
return context.prisma.mutation.updatePost(
{
data: {
categories: {
connect: ids
}
},
where: {
id: args.postId
}
},
info,
)
},
// ... other resolvers
}
}
Implementing the addPostToCategories resolver with Prisma Client

To get the same behaviour with Prisma Client, you'll need to adjust your resolver implementation:

1const resolvers = {
2 Mutation: {
3 addPostToCategories: (_, args, context, info) => {
4 const ids = args.categoryIds.map(id => ({ id }))
5 return context.db.post.update({
6 where: {
7 id: args.postId
8 },
9 data: {
10 categories: { connect: ids }
11 }
12 })
13 },
14 // ... other resolvers
15 }
16}

You can now send the respective query via the GraphQL Playground:

mutation {
addPostToCategories(
postId: "__AUTHOR_ID__"
categoryIds: [
"__CATEGORY_ID_1__",
"__CATEGORY_ID_2__",
]
) {
id
title
categories {
id
name
}
}
}

4. Cleaning up

Since the entire app has now been upgraded to Prisma 2.0, 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:

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

4.2. Delete unused files

Next, delete the files of your Prisma 1 setup:

rm 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