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 drop them in the#prisma1-community
channel in the Prisma Slack.
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:
Then, you can install the latest @nexus/schema
dependency in your project:
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):
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:
Note however that you can still invoke the Prisma 2 CLI with the familiar command:
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
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({34 // Provide all the GraphQL types we've implemented5 types: [Query, Mutation, UserUniqueInput, User, Post, Category, Profile],67 // Configure the interface to Prisma- prisma: {- datamodelInfo,- client: prisma,- },+ plugins: [nexusSchemaPrisma({+ experimentalCRUD: true,+ })],1516 // Specify where Nexus should put the generated files17 outputs: {18 schema: path.join(__dirname, './generated/schema.graphql'),19 typegen: path.join(__dirname, './generated/nexus.ts'),20 },2122 // Configure nullability of input arguments: All arguments are non-nullable by default23 nonNullDefaults: {24 input: false,25 output: false,26 },2728 // Configure automatic type resolution for the TS representations of the associated types29 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'34export interface Context {- prisma: Prisma+ prisma: PrismaClient7}
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.:
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: Stringemail: String}type Query {posts(searchString: String): [Post!]!user(userUniqueInput: UserUniqueInput!): Userusers(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!): PostupdateBio(userUniqueInput: UserUniqueInput!, bio: String!): UseraddPostToCategories(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 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/generatedrm -rf prisma1
5.3. Stop the Prisma server
Finally, you can stop running your Prisma server.