prisma-binding to 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, join us on our Discord.
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 be migrated to @nexus/schema
and the nexus-plugin-prisma
. As opposed to the SDL-first approach that's used with prisma-binding
, Nexus follows a code-first approach to construct GraphQL schemas. You can learn about the main differences of these two approaches in this article. If you want to continue using the SDL-first approach, you can follow the guide to upgrade from prisma-binding
to an SDL-first setup.
This guide also explains how to migrate from JavaScript to TypeScript, it therefore basically assumes a full rewrite of your existing app. If you want to keep running your application in JavaScript, you can ignore the instructions that relate to the TypeScript setup keep using JavaScript as before.
The guide assumes that you already went through the guide for upgrading the Prisma ORM layer. This means you already:
- installed the Prisma ORM 2.0 CLI
- created your Prisma ORM 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 ORM 2.0 schema - A folder called
src
with your application code and a schema calledschema.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. Installing and configuring Nexus
1.1. Install Nexus dependencies
The first step is to install the Nexus dependency in your project:
npm install @nexus/schema
Next, install the the Prisma ORM plugin for Nexus which will allow you to expose Prisma models in your GraphQL API:
npm install nexus-plugin-prisma
The nexus-plugin-prisma
dependency bundles all required Prisma ORM dependencies. You should therefore remove the dependencies that you installed when you upgraded the Prisma ORM layer of your app:
npm uninstall @prisma/cli @prisma/client
Note however that you can still invoke the Prisma ORM 2.0 CLI with the familiar command:
npx prisma
1.2. Configure TypeScript
Since you'll be using TypeScript in this guide, you need to add the required dependencies:
npm install typescript ts-node-dev --save-dev
Create a new file named tsconfig.json
in the root directory of your project:
touch tsconfig.json
Now add the following contents to the new file:
{
"compilerOptions": {
"skipLibCheck": true,
"strict": true,
"rootDir": "src",
"noEmit": true
},
"include": ["src/**/*"]
}
1.3. Create your basic Nexus setup
Create the root source file of your API called index.ts
inside the src
directory:
touch src/index.ts
Note that for this guide, you'll write the entire application inside of index.ts
. In practice, you probably want to split your GraphQL types across different files as shown in this example.
For some basic setup, add this code to index.ts
:
import { queryType, makeSchema } from '@nexus/schema'
import { nexusSchemaPrisma } from 'nexus-plugin-prisma/schema'
import { GraphQLServer } from 'graphql-yoga'
import { createContext } from './context'
const Query = queryType({
definition(t) {
t.string('hello', () => {
return 'Hello Nexus!'
})
},
})
export const schema = makeSchema({
types: [Query],
plugins: [nexusSchemaPrisma({ experimentalCRUD: true })],
outputs: {
schema: __dirname + '/../schema.graphql',
typegen: __dirname + '/generated/nexus.ts',
},
typegenAutoConfig: {
contextType: 'Context.Context',
sources: [
{
source: '@prisma/client',
alias: 'prisma',
},
{
source: require.resolve('./context'),
alias: 'Context',
},
],
},
})
new GraphQLServer({ schema, context: createContext() }).start(() =>
console.log(`Server ready at: http://localhost:4000`)
)
Note that this setup already contains the configuration of the Prisma ORM plugin for Nexus. This will enable the t.model
and t.crud
functionality that you'll get to know later in this guide.
In the typegenAutoConfig
setting, you're providing additional types that help your editor to provide your autocompletion as you develop your app. Right now it references a file named context.ts
that you don't have in your project yet. This file will contain the type of your context
object that's passed through your GraphQL resolver chain.
Create the new context.ts
file inside the src
directory:
touch src/context.ts
Now add the following code to it:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export interface Context {
prisma: PrismaClient
}
export function createContext(): Context {
return { prisma }
}
Next, adjust the scripts
section inside your package.json
to include the following commands:
{
"scripts": {
"start": "node dist/server",
"clean": "rm -rf dist",
"build": "npm -s run clean && npm -s run generate && tsc",
"generate": "npm -s run generate:prisma && npm -s run generate:nexus",
"generate:prisma": "prisma generate",
"generate:nexus": "ts-node --transpile-only src/schema",
"dev": "ts-node-dev --no-notify --respawn --transpile-only src"
}
}
The dev
script starts a development server that you always should have running in the background when developing your app. This is important because of the code generation Nexus performs in the background.
You can start the development server using the following command:
npm run dev
You should see the following CLI output:
Server ready at: http://localhost:4000
Your GraphQL server is now running at http://localhost:4000. So far it implements a single GraphQL query that you can send as follows:
{
hello
}
In the following steps, we'll explain how you can migrate your existing SDL-first GraphQL schema that's implemented with prisma-binding
to an equivalent setup using Nexus.
2. Create your GraphQL types
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.
In Nexus, GraphQL types are defined via the objectType
function. Import objectType
and then start with the skeleton for your first GraphQL type. In this case, we're starting by mapping Prisma schema's User
model to GraphQL:
import { objectType } from 'nexus'
const User = objectType({
name: 'User',
definition(t) {
// the fields of the type will be defined here
},
})
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 lets you expose your Prisma models via GraphQL.
In that manner, you can start completing your object type definition until you exposed all the fields of the model:
const User = objectType({
name: 'User',
definition(t) {
t.model.id()
t.model.email()
t.model.name()
t.model.jsonData()
t.model.role()
t.model.profile()
t.model.posts()
},
})
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:
const User = objectType({
name: 'User',
definition(t) {
t.model.id()
t.model.email()
t.model.name()
t.model.jsonData()
t.model.role()
t.model.profile()
t.model.posts({
filtering: true,
ordering: true,
})
},
})
After defining a type using the objectType
function, you also need to manually add it to your GraphQL schema that you're building with Nexus. You can do it by adding it to the types
which are provided as an option to the makeSchema
function:
export const schema = makeSchema({
types: [Query, User],
plugins: [nexusSchemaPrisma()],
outputs: {
schema: __dirname + '/../schema.graphql',
typegen: __dirname + '/generated/nexus.ts',
},
typegenAutoConfig: {
sources: [
{
source: '@prisma/client',
alias: 'prisma',
},
],
},
})
Once you're done with the first type, you can start defining the remaining ones.
Expand to view the full version of the sample data model
To expose all sample Prisma models with Nexus, the following code is needed:
const User = objectType({
name: 'User',
definition(t) {
t.model.id()
t.model.email()
t.model.name()
t.model.jsonData()
t.model.role()
t.model.profile()
t.model.posts({
filtering: true,
ordering: true,
})
},
})
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.createdAt()
t.model.updatedAt()
t.model.title()
t.model.content()
t.model.published()
t.model.author()
t.model.authorId()
t.model.categories({
filtering: true,
ordering: true,
})
},
})
const Profile = objectType({
name: 'Profile',
definition(t) {
t.model.id()
t.model.bio()
t.model.userId()
t.model.user()
},
})
const Category = objectType({
name: 'Category',
definition(t) {
t.model.id()
t.model.name()
t.model.posts({
filtering: true,
ordering: true,
})
},
})
Be sure to include all newly defined types in the types
option that's provided to makeSchema
:
export const schema = makeSchema({
types: [Query, User, Post, Profile, Category],
plugins: [nexusSchemaPrisma()],
outputs: {
schema: __dirname + '/../schema.graphql',
typegen: __dirname + '/generated/nexus.ts',
},
typegenAutoConfig: {
sources: [
{
source: '@prisma/client',
alias: 'prisma',
},
],
},
})
You can view the current version of your GraphQL schema in SDL in the generated GraphQL schema file in ./schema.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:
# import Post from './generated/prisma.graphql'
# import User from './generated/prisma.graphql'
# import Category from './generated/prisma.graphql'
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
}
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: Enumerable<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
},
}
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:
const Query = queryType({
definition(t) {
t.crud.users({
filtering: true,
ordering: true,
})
},
})
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 schema.graphql
file:
type Query {
users(after: UserWhereUniqueInput, before: UserWhereUniqueInput, first: Int, last: Int, orderBy: Enumerable<UserOrderByInput>, skip: Int, where: UserWhereInput): [User!]!
}
You can now write your first query against the new API, e.g.:
{
users {
id
name
profile {
id
bio
}
posts {
id
title
categories {
id
name
}
}
}
}
If your application exposes all CRUD operations from Prisma ORM 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.2. 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
},
}
Code-first schema definition with nexus
To get the same behavior with Nexus, you'll need to add a t.field
definition to the queryType
:
const Query = queryType({
definition(t) {
// ... previous queries
t.list.field('posts', {
type: 'Post',
nullable: false,
args: { searchString: stringArg() },
})
},
})
Although this code gives probably gives you a type error in your editor, you can already look at the generated SDL version of your GraphQL schema inside schema.graphql
. You'll notice that this has added the correct definition to your GraphQL schema already:
type Query {
posts(searchString: String): [Post!]!
users(after: UserWhereUniqueInput, before: UserWhereUniqueInput, first: Int, last: Int, orderBy: Enumerable<UserOrderByInput>, skip: Int, where: UserWhereInput): [User!]!
}
However, the code is missing the actual resolver logic. This is what you're going to add next.
Resolver implementation with nexus
You can add the resolver with Nexus as follows:
const Query = queryType({
definition(t) {
// ... previous queries
t.list.field('posts', {
type: 'Post',
nullable: false,
args: { searchString: stringArg() },
resolve: (_, args, context) => {
return context.prisma.post.findMany({
where: {
OR: [
{
title: { contains: args.searchString },
},
{
content: { contains: args.searchString },
},
],
},
})
},
})
},
})
To validate the implementation, you can now e.g. send the following example query to your GraphQL server:
{
posts {
id
title
author {
id
name
}
}
}
3.1.2. 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
}
Note that this is a bit of a contrived example to demonstrate the usage of input
types with Nexus.
Resolver implementation with prisma-binding
const resolvers = {
Query: {
user: (_, args, context, info) => {
return context.prisma.query.user(
{
where: args.userUniqueInput,
},
info
)
},
// ... other resolvers
},
}
Code-first schema definition with nexus
To get the same behavior 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:
import { inputObjectType, arg } from '@nexus/schema'
const UserUniqueInput = inputObjectType({
name: 'UserUniqueInput',
definition(t) {
t.string('id')
t.string('email')
},
})
const Query = queryType({
definition(t) {
// ... previous queries
t.field('user', {
type: 'User',
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
},
})
},
})
Since UserUniqueInput
is a new type in your GraphQL schema, you again need to add it to the types
option that's passed to makeSchema
:
export const schema = makeSchema({
types: [Query, User, Post, Profile, Category, UserUniqueInput],
plugins: [nexusSchemaPrisma()],
outputs: {
schema: __dirname + '/../schema.graphql',
typegen: __dirname + '/generated/nexus.ts',
},
typegenAutoConfig: {
sources: [
{
source: '@prisma/client',
alias: 'prisma',
},
],
},
})
If you look at the generated SDL version of your GraphQL schema inside schema.graphql
, you'll notice that this change already added the correct definition to your GraphQL schema:
type Query {
posts(searchString: String): [Post!]
user(userUniqueInput: UserUniqueInput!): User
users(after: UserWhereUniqueInput, before: UserWhereUniqueInput, first: Int, last: Int, orderBy: Enumerable<UserOrderByInput>, skip: Int, where: UserWhereInput): [User!]!
}
input UserUniqueInput {
email: String
id: String
}
You can even send the respective query via the GraphQL Playground already:
{
user(userUniqueInput: { email: "alice@prisma.io" }) {
id
name
}
}
However, because the resolver is not yet implemented you will not get any data back yet.
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:
const UserUniqueInput = inputObjectType({
name: 'UserUniqueInput',
definition(t) {
t.string('id')
t.string('email')
},
})
const Query = queryType({
definition(t) {
// ... previous queries
t.field('user', {
type: 'User',
nullable: true,
args: {
userUniqueInput: arg({
type: 'UserUniqueInput',
nullable: false,
}),
},
resolve: (_, args, context) => {
return context.prisma.user.findUnique({
where: {
id: args.userUniqueInput?.id,
email: args.userUniqueInput?.email,
},
})
},
})
},
})
If you're re-sending the same query from before, you'll find that it now returns actual data.
3.2. Migrate GraphQL mutations
In this section, you'll migrate the GraphQL mutations from the sample schema to the Nexus.