apps/blog/content/blog/using-graphql-nexus-with-a-database-pmyl3660ncst/index.mdx
GraphQL Nexus is a code-first, type-safe GraphQL schema construction library for
JavaScript/TypeScript. Learn how it can be connected to a database using the Prisma client & the new
nexus-prisma plugin.
⚠️ This article is outdated as it relates to Prisma 1 which is now deprecated. To learn more about the most recent version of Prisma, read the documentation. ⚠️
In the last article, we introduced GraphQL Nexus, a GraphQL library that enables code-first development for TypeScript and JavaScript. With Nexus, the GraphQL schema is defined and implemented programmatically. It therefore follows proven approaches of GraphQL servers in other languages, such as sangria-graphql (Scala), graphlq-ruby or graphene (Python).
Today's article is about connecting your Nexus-based GraphQL server to a database, using the Prisma client as well as the new nexus-prisma plugin. We'll later walk you through a practical example of building a GraphQL API for a blogging app from scratch.
nexus-prismaworks with PostgreSQL, MySQL and MongoDB. Find the docs for it here.
nexus-prisma pluginapollo-server, graphql-yoga, ...)nexus-prisma workflowIf you haven't worked with Prisma before, here's a quick rundown of how it works:
nexus-prisma plugin under the hoodWhen adding nexus-prisma to the mix, there's another step: invoking the nexus-prisma-generate codegen CLI. It generates the building blocks of a full-blown GraphQL CRUD API for your Prisma models, e.g. for a User model it includes:
user(...): User!: Fetches a single recordusers(...): [User!]!: Fetches a list of recordsusersConnection(...): UserConnection!: Relay connections & aggregationscreateUser(...): User!: Creates a new recordupdateUser(...): User: Updates a recorddeleteUser(...): User: Deletes a recordupdatesManyUsers(...): BatchPayload!: Updates many records in bulkdeleteManyUsers(...): BatchPayload!: Deletes many records in bulkUserCreateInput: Wraps all fields of the recordUserUpdateInput: Wraps all fields of the recordUserWhereInput: Provides filters for all fields of the recordUserWhereUniqueInput: Provides filters for unique fields of the recordUserUpdateManyMutationInput: Wraps fields that can be updated in bulkUserOrderByInput: Specifies ascending or descending orders by field
UserCreateInputandUserUpdateInputdiffer in the way relation fields are treated.
When writing your GraphQL server code with nexus and nexus-prisma, you build upon these operations by exposing and customizing them to your own API needs:
After having generated the CRUD building blocks, you can use prismaObjectType from nexus-prisma to start exposing (and customizing) them. The following code snippets depict an implementation that provides a GraphQL API for a TODO-list app based on Prisma and nexus-prisma:
type Todo {
id: ID! @unique
title: String!
done: Boolean! @default(value: "false")
}
import { prismaObjectType } from 'nexus-prisma'
import { idArg } from 'nexus-prisma'
// Expose the full "Query" building block
const Query = prismaObjectType({
name: 'Query',
definition: t => t.prismaFields(['*'])
})
// Customize the "Mutation" building block
const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
// Keep only the `createTodo` mutation
t.prismaFields(['createTodo'])
// Add a custom `markAsDone` mutation
t.field('markAsDone', {
args: { id: idArg() },
nullable: true,
resolve: (_, { id }, ctx) {
return ctx.prisma.updateTodo({
where: { id },
data: { done: true }
})
}
})
}
})
const schema = makePrismaSchema({
types: [Query, Mutation],
// More config stuff, e.g. where to put the generated SDL
})
// Feed the `schema` into your GraphQL server, e.g. `apollo-server, `graphql-yoga`
# The fully exposed "Query" building block
type Query {
todo(where: TodoWhereUniqueInput!): Todo
todoes(
after: String
before: String
first: Int
last: Int
orderBy: TodoOrderByInput
skip: Int
where: TodoWhereInput
): [Todo!]!
todoesConnection(
after: String
before: String
first: Int
last: Int
orderBy: TodoOrderByInput
skip: Int
where: TodoWhereInput
): TodoConnection!
}
# The customized "Mutation" building block
type Mutation {
createTodo(data: TodoCreateInput!): Todo!
markAsDone(id: ID): Todo
}
# The Prisma model
type Todo {
done: Boolean!
id: ID!
title: String!
}
# More of the generated building blocks:
# e.g. `TodoWhereUniqueInput`, `TodoCreateInput`, `TodoConnection`, ...
We're applying prismaObjectType to Query and Mutation. For Query, we're keeping all fields (i.e. todo, todoes and todoesConnection). For Mutation we're using prismaFields to customize the exposed operations.
prismaFields lets us select the operations to be exposed. In this case, we only want to keep the operation to create a model (createTodo). From the generated CRUD building blocks, we're including neither updateTodo nor deleteTodo but implement our own markAsDone(id: ID!) mutation that checks off a certain Todo.
Let's now take a quick tour through a standard Prisma use case and see how to quickly build a GraphQL API for a blogging app in a few easy steps. Here's what we'll do:
nexus-prismanexus-prismaIf you want to follow along, you need to have the Prisma CLI installed:
npm install -g prisma
yarn global add prisma
brew tap prisma/prisma
brew install prisma
nexus and nexus-prismaThis section mostly deals with your project setup. Feel free to skip it if you don't want to code along, otherwise expand the section below.
<Accordions type="single"> <Accordion title="See project setup instructions"> Use the Prisma CLI to create a simple Prisma project:prisma init myblog
In the interactive prompt, select the following options:
As an alternative to the Demo server, you can also run Prisma locally using Docker.
Next you need to configure your nexus-prisma workflow. Add the following dependencies (inside the myblog directory):
npm init -y
npm install --save nexus graphql nexus-prisma prisma-client-lib graphql-yoga
npm install --save-dev typescript ts-node-dev
Next, add the following two lines to the end of your prisma.yml:
hooks:
post-deploy:
- prisma generate
- npx nexus-prisma-generate --client ./generated/prisma-client --output ./generated/nexus-prisma
This ensures that the Prisma client as well as the generated nexus-prisma CRUD building blocks are being updated whenever you make changes to your models.
Since we're using TypeScript, let's quickly add a tsconfig.json:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": ["esnext", "dom"]
}
}
Finally, go ahead and add a start script that you'll use for development. It starts a development server that watches your files in the background and updates the generated SDL and Nexus typings as you code. Add this to your package.json:
"scripts": {
"start": "ts-node-dev --no-notify --respawn --transpileOnly ./"
},
The prisma init command created a default User model in datamodel.prisma. As we're building a blogging application, let's adjust the models to our application domain:
type User {
id: ID! @unique
email: String! @unique
name: String
posts: [Post!]!
}
type Post {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
published: Boolean! @default(value: "false")
title: String!
content: String
author: User!
}
Next you need to migrate the database by applying the datamodel to it. Using the following command, each model defined in datamodel.prisma will be mapped to a table in the underlying database:
prisma deploy
Because you configured the post-deploy hook in prisma.yml earlier, your Prisma client and the CRUD building blocks are automatically updated.
nexus-prismaIn the early phase of a project, it's often helpful to have full CRUD capabilities exposed by an API – more constrained API requirements typically emerge over time. nexus-prisma perfectly accounts for that by providing a straightforward path to go from full CRUD to customized API operations.
Let's start with a GraphQL API that exposes full CRUD for the defined models (note that this includes filters, pagination and sorting). Create a new file called index.ts and add the following code to it:
import * as path from 'path'
import { GraphQLServer } from 'graphql-yoga'
import { makePrismaSchema, prismaObjectType } from 'nexus-prisma'
import { prisma } from './generated/prisma-client'
import datamodelInfo from './generated/nexus-prisma'
const Query = prismaObjectType({
name: 'Query',
definition: t => t.prismaFields(['*']),
})
const Mutation = prismaObjectType({
name: 'Mutation',
definition: t => t.prismaFields(['*']),
})
const schema = makePrismaSchema({
types: [Query, Mutation],
prisma: {
datamodelInfo,
client: prisma,
},
outputs: {
schema: path.join(__dirname, './generated/schema.graphql'),
typegen: path.join(__dirname, './generated/nexus.ts'),
},
})
const server = new GraphQLServer({
schema,
context: { prisma },
})
server.start(() => console.log(`Server is running on http://localhost:4000`))
We're not paying much attention to file structure in this short tutorial. Check our
graphql-authexample for a proper setup and modularized schema.
After running npm run start, you can open the GraphQL Playground for your GraphQL server on http://localhost:4000. With the little code you wrote above, you already have a full-blown GraphQL CRUD API at your disposal.
# Fetch all posts with their authors
query {
posts {
id
title
published
author {
id
name
}
}
}
# Fetch a certain user by email
query {
user(where: { email: "[email protected]" }) {
id
name
posts {
id
title
}
}
}
# Create a post and its author
mutation {
createPost(data: { title: "Hello World", author: { create: { email: "[email protected]" } } }) {
id
published
author {
id
name
}
}
}
# Update the name of a user
mutation {
updateUser(data: { name: "Alice" }, where: { email: "[email protected] " }) {
id
name
}
}
# Delete a user
mutation {
deleteUser(where: { email: "[email protected] " }) {
id
name
}
}
How does that work? nexus-prisma-generate generated a GraphQL schema that provides a CRUD API for Prisma models (your CRUD building blocks). This GraphQL schema follows the OpenCRUD specification. Using the prismaObjectType function, you can now expose and customize the operations of that schema.
prismaObjectType and prismaFields use a whitelist approach, meaning you need to explicitly list the fields you want to expose. The wildcard operator * includes all fields.
nexus-prismaIn this section, we'll learn how the CRUD GraphQL API from nexus-prisma can be customized. Specifically, we are going to:
User modelPost modelcreatePost and updatePost mutationscreateDraft and publish mutationsUser modelIn this section, we'll hide the email field from the User model.
To customize a model, we need to apply the prismaObjectType function to it and pass the definition(t) function as an option:
const User = prismaObjectType({
name: 'User',
definition(t) {
t.prismaFields(['id', 'name', 'posts'])
},
})
By calling prismaFields on the model t, we can customize the exposed fields (and their arguments). Because email is not included in the list, it's removed from our GraphQL API.
To apply the changes, you need to explicitily pass User to the types array inside of makePrismaSchema:
const schema = makePrismaSchema({
types: [Query, Mutation, User],
// ...
}
Note that your editor is able to suggest what to pass into prismaObjectType and prismaFields based on the generated CRUD building blocks. This means when you type prismaObjectType('') and hit ctrl+space, it suggests the names of all generated CRUD building blocks. When calling t.prismaFields(['']), it suggests the fields of t:
Post modelThe new code-first approach with nexus-prisma also makes it easy to add computed fields to Prisma models. Say you want to add a field to Post that always returns the title spelled entirely uppercased. Here's how to implement that:
const Post = prismaObjectType({
name: 'Post',
definition(t) {
t.prismaFields(['*'])
t.string('uppercaseTitle', {
resolve: ({ title }, args, ctx) => title.toUpperCase(),
})
},
})
We add a new field to our model using the t.string(...) API that comes from graphql-nexus. Because it's a computed field (and therefore can't be automatically resolved by nexus-prisma), we also need to attach a resolver to it.
As before, you need to explicitly add the customized Post model to the types array:
const schema = makePrismaSchema({
types: [Query, Mutation, User, Post],
// ...
}
createPost and updatePost mutationsIn the same way that we've hidden the email field from the User model, we can also hide operations from the Query/Mutation types that are part of the generated nexus-prisma CRUD building blocks.
To hide createPost and updatePost we again need to pass definition(t) to prismaObjectType as an option. Note that once we call prismaFields on a type, we need to explicitly list all fields we want to keep (an empty array would be interpreted as "keep no operations"):
const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
t.prismaFields(['createUser', 'updateUser', 'deleteUser', 'deletePost'])
},
})
The generated CRUD GraphQL API also includes batched and upsert operations that we're excluding here for brevity as well.
createDraft and publish mutationsFinally, we're adding two custom mutations to our GraphQL API. Here's what their SDL definitions are supposed to look like:
type Mutation {
createDraft(title: String!, content: String): Post!
publish(id: ID!): Post
}
To implement these mutations, you need to add two fields (by calling t.field( ... ) from the nexus API) to the Mutation type:
const Mutation = prismaObjectType({
name: 'Mutation',
definition(t) {
t.prismaFields(['createUser', 'updateUser', 'deleteUser', 'deletePost'])
t.field('createDraft', {
type: 'Post',
args: {
title: stringArg(),
content: stringArg({ nullable: true }),
},
resolve: (parent, { title, content }, ctx) => {
return ctx.prisma.createPost({ title, content })
},
})
t.field('publish', {
type: 'Post',
nullable: true,
args: {
id: idArg(),
},
resolve: (parent, { id }, ctx) => {
return ctx.prisma.updatePost({
where: { id },
data: { published: true },
})
},
})
},
})
Be sure to import stringArg and idArg from the nexus package to make this work.
GraphQL Nexus also generates the SDL version of your GraphQL schema, you find it in ./generated/schema.graphql. The final version of our GraphQL API looks as follows:
type Mutation {
createDraft(content: String, title: String): Post!
createUser(data: UserCreateInput!): User!
deletePost(where: PostWhereUniqueInput!): Post
deleteUser(where: UserWhereUniqueInput!): User
publish(id: ID): Post
updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
}
type Query {
node(id: ID!): Node
post(where: PostWhereUniqueInput!): Post
posts(
after: String
before: String
first: Int
last: Int
orderBy: PostOrderByInput
skip: Int
where: PostWhereInput
): [Post!]!
postsConnection(
after: String
before: String
first: Int
last: Int
orderBy: PostOrderByInput
skip: Int
where: PostWhereInput
): PostConnection!
user(where: UserWhereUniqueInput!): User
users(
after: String
before: String
first: Int
last: Int
orderBy: UserOrderByInput
skip: Int
where: UserWhereInput
): [User!]!
usersConnection(
after: String
before: String
first: Int
last: Int
orderBy: UserOrderByInput
skip: Int
where: UserWhereInput
): UserConnection!
}
type User {
id: ID!
name: String
posts(
after: String
before: String
first: Int
last: Int
orderBy: PostOrderByInput
skip: Int
where: PostWhereInput
): [Post!]
}
type Post {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
author: User!
content: String
published: Boolean!
title: String!
uppercaseTitle: String!
}
# More generated SDL types ...
This was the last part of our articles series on code-first GraphQL server development.
GraphQL Nexus and the nexus-prisma plugin implement the learnings we've gathered from being active contributors to the GraphQL ecosystem for well over two years. After having found too many issues with an SDL-first approach, we're incredibly excited about the new code-first tooling that's currently emerging.
We strongly believe that GraphQL Nexus (and other code-first approaches such as TypeGraphQL) are going to drastically change the way how GraphQL schemas will be built in the future.
Here are our main takeways from the series:
nexus-prisma plugin builds on top of the Prisma models and lets developers build a GraphQL API by exposing and customizing auto-generated CRUD building blocks.nexus-prisma today 🙌There are several ways for you to try out nexus-prisma. You can either follow the Getting Started-section in the docs or explore our TypeScript GraphQL examples.
Please share your feedback by opening a GitHub issue or reaching out in our Slack.
A huge shoutout to our open-source engineer Flavian Desverne for his incredible work on the nexus-prisma plugin 💪✨