February 12, 2019

Using GraphQL Nexus with a Database

nikolasburk
Nikolas Burk
@nikolasburk
Join the
discussion
10

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.

Recap: Code-first development with GraphQL Nexus

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-prisma works with PostgreSQL, MySQL and MongoDB. Find the docs for it here.


TLDR: Benefits of the nexus-prisma plugin

  • CRUD operations for your Prisma models in GraphQL
  • Customize your Prisma models, e.g. hide certain fields or add computed fields
  • Full type-safety: Coherent set of types for GraphQL schema and database
  • Compatible with the GraphQL ecosystem (e.g. apollo-server, graphql-yoga, ...)

Understanding the nexus-prisma workflow

The Prisma client as an ORM replacement

If you haven't worked with Prisma before, here's a quick rundown of how it works:

  1. Define your datamodel or let Prisma introspect your existing database
  2. Generate your Prisma client, i.e. a type-safe database client
  3. Use the Prisma client to access your database in an application (e.g. a GraphQL API)

The nexus-prisma plugin under the hood

When 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:

  • Queries
    • user(...): User!: Fetches a single record
    • users(...): [User!]!: Fetches a list of records
    • usersConnection(...): UserConnection!: Relay connections & aggregations
  • Mutations
    • createUser(...): User!: Creates a new record
    • updateUser(...): User: Updates a record
    • deleteUser(...): User: Deletes a record
    • updatesManyUsers(...): BatchPayload!: Updates many records in bulk
    • deleteManyUsers(...): BatchPayload!: Deletes many records in bulk
  • GraphQL input types
    • UserCreateInput: Wraps all fields of the record
    • UserUpdateInput: Wraps all fields of the record
    • UserWhereInput: Provides filters for all fields of the record
    • UserWhereUniqueInput: Provides filters for unique fields of the record
    • UserUpdateManyMutationInput: Wraps fields that can be updated in bulk
    • UserOrderByInput: Specifies ascending or descending orders by field

UserCreateInput and UserUpdateInput differ 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")
}

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.


Example: From standard CRUD to a customized GraphQL API

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:

  1. Setup a Prisma project (using a free demo database) with TypeScript
  2. Define models, migrate database and generate the Prisma client
  3. Expose full CRUD GraphQL API via nexus-prisma
  4. Customize the GraphQL API via nexus-prisma

If you want to follow along, you need to have the Prisma CLI installed:

npm install -g prisma
Copy

1) Setup Prisma project with TypeScript, nexus and nexus-prisma

This 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.

Use the Prisma CLI to create a simple Prisma project:

prisma init myblog
Copy

In the interactive prompt, select the following options:

  1. Select Demo server (includes a free & hosted demo database in Prisma Cloud)
  2. Authenticate with Prisma Cloud in your browser (if necessary)
  3. Back in your terminal, confirm all suggested values

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
Copy

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
Copy

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"]
  }
}
Copy

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 ./"
},
Copy

2) Define models, migrate database & generate the Prisma client

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!
}
Copy

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
Copy

Note that Prisma will soon have a more powerful migration system. Learn more.

Because you configured the post-deploy hook in prisma.yml earlier, your Prisma client and the CRUD building blocks are automatically updated.

3) Expose full CRUD GraphQL API via nexus-prisma

In 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`))
Copy

We're not paying much attention to file structure in this short tutorial. Check our graphql-auth example 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.

Example queries
# Fetch all posts with their authors
query {
  posts {
    id
    title
    published
    author {
      id
      name
    }
  }
}
# Fetch a certain user by email
query {
  user(where: { email: "alice@prisma.io" }) {
    id
    name
    posts {
      id
      title
    }
  }
}
Example mutations
# Create a post and its author
mutation {
  createPost(data: { title: "Hello World", author: { create: { email: "bob@prisma.io" } } }) {
    id
    published
    author {
      id
      name
    }
  }
}
# Update the name of a user
mutation {
  updateUser(data: { name: "Alice" }, where: { email: "alice@prisma.io " }) {
    id
    name
  }
}
# Delete a user
mutation {
  deleteUser(where: { email: "alice@prisma.io " }) {
    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.

4) Customize the GraphQL API via nexus-prisma

In this section, we'll learn how the CRUD GraphQL API from nexus-prisma can be customized. Specifically, we are going to:

  1. Hide a field from the User model
  2. Add a computed field to the Post model
  3. Hide the createPost and updatePost mutations
  4. Add two custom createDraft and publish mutations
4.1) Hide a field from the User model

In 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'])
  },
})
Copy

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],

  // ...
}
Copy

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:

4.2) Add a computed field to the Post model

The 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(),
    })
  },
})
Copy

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],

  // ...
}
Copy
4.3) Hide createPost and updatePost mutations

In 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'])
  },
})
Copy

The generated CRUD GraphQL API also includes batched and upsert operations that we're excluding here for brevity as well.

4. Add two custom createDraft and publish mutations

Finally, 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 },
        })
      },
    })
  },
})
Copy

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

3 key takeways from our code-first GraphQL articles

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:

  1. Code-first approaches let you build GraphQL servers in language-idiomatic ways without the need for additional tooling ("the only tool you need is your programming language") while retaining the benefits of SDL as a communication tool.
  2. GraphQL Nexus lets developers construct their schemas with a flexible and type-safe API. Developers get an amazing experience thanks to auto-completion and build-time error checks.
  3. The 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.

Try out 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 💪✨