Overview

So far we have been working with in-memory data while we learn about other parts of Nexus in a focused manner, but in this chapter we're going to put the focus squarely on data and show how Nexus can be used with a database. This marks an important step toward your blog app becoming more real. You'll learn about:

  • Prisma
  • Nexus Plugins
  • Setting up a Postgres database locally

We're going to be using a database called Postgres and a tool called Prisma to interact with it.

Postgres is a well known open-source relational database. Prisma is a new way of working with databases that we'll learn more about in a moment.

Its important to understand that Nexus does not require these technology choices and could actually be used with any database and abstractions over them (raw SQL, query builder, ORM..). However, Nexus is built by a team at Prisma (the company) and unsurprisingly there is great integration between its tools and Nexus.

What is Prisma?

So, what is Prisma? It is an open source database toolkit that consists of the following parts:

  • Prisma Client: Auto-generated and type-safe query builder for Node.js & TypeScript
  • Prisma Migrate (experimental): Declarative data modeling & migration system
  • Prisma Studio (experimental): GUI to view and edit data in your database

At the heart of Prisma is the Prisma Schema, a file usually called schema.prisma, that you will see later in this tutorial. It is a declarative file wherein using a domain specific language you encode your database schema, connection to the database, and more.

Prisma has great docs so definitely check them out at some point. For now you can stay in the flow of this tutorial if you want though. We're going to focus on Prisma Client.

Nexus plugins?

Now we're ready to actually use Prisma with Nexus! This is going to be achieved by way of a Nexus plugin. What? Yep, Nexus has a plugin ecosystem which can be used to enhance your app and development experience in a number of ways! Nexus Plugins broadly breakdown into three areas of enhancement:

  • worktime: watch new file types, hook onto events, attach new CLI commands, ...
  • runtime: add schema middleware, setting presets, field builders, custom scalars, ...
  • testtime: attach data to the Nexus Test Context

Plugin packages are usually named after the following convention: nexus-plugin-<name> and we refer to them just by either name suffix part. So for the example when we say that Prisma and Nexus integrate together via the Nexus prisma plugin that means nexus-plugin-prisma.

Another convention that they usually follow is that they use the default export of the package as well as a named export that is a camel case variant of the name part mentioned before (see below for example).

Plugins are easy to use:

$npm add nexus-plugin-foo
1import foo from 'nexus-plugin-foo'
2import { foo } from 'nexus-plugin-foo' // alternative, as you wish
3import { use } from 'nexus'
4
5use(foo())

All plugins are functions. They can accept optional or required settings if the plugin authors wishes so. Once a plugin is imported and invoked, its return value is passed to the Nexus use method. Once done, your app is using the plugin. On the surface the method looks similar to that of express's use.

Note how the plugins only exist within your app code yet we mentioned before that plugins can augment worktime aspects like the CLI. Neat right? No external config files with plugin setup are required. Its one of many examples of how Nexus provides a powerful and extensible system without forwarding you, the user, a complexity tax for it.

Connect to your database

Now that you know a bit about Prisma and Nexus plugins, let's get going! Do the following:

  • Install the Prisma plugin
  • Use it in your api/app.ts module
  • Create your Prisma Schema
  • Create a .env file to store your database credentials
  • Connect to your database

like so:

$npm add nexus-plugin-prisma

Note: Like we mentioned in Chapter 1, the simplicity of the dependency story here is no accident. Nexus Prisma plugin bundles deps like @prisma/client for you. If you're curious about Nexus' philosophy on dependency management we've written about it here.

1// api/app.ts
2
3import { use } from 'nexus'
4import { prisma } from 'nexus-plugin-prisma'
5
6use(prisma())
$npx prisma init
1// prisma/schema.prisma
2
3datasource postgres {
4 provider = "postgresql"
5 url = env("DATABASE_URL")
6}
1# prisma/.env
2DATABASE_URL="<postgres_connection_url>"

Almost done, but we still need to setup a Postgres database for our app to connect to. There are a ton of ways to do this so we're just going to show the most straight forward cross-platform way we know how. First, make sure you have docker installed. Then, simply run this command:

$docker run --detach --publish 5432:5432 -e POSTGRES_PASSWORD=postgres --name postgres postgres:10.12

That's it. You now have a Postgres server running. You can connect to it at a URL like:

1postgresql://postgres:postgres@localhost:5432/myapp

If you prefer setting up your local Postgres another way go for it. If our suggest approach doesn't work for you, then checkout a few other approaches listed on the Nexus recipes page.

Finally, in the prisma/.env file you've created before, replace <postgres_connection_url> with your actual database URL.

Create your database schema

It is now time to replace our in-memory data with actual tables in our database. To do this we'll write models in our Prisma Schema.

In chapters 2 and 3 we already began to model our blog domain with the GraphQL type Post. We can base our models on that prior work, resulting in a Prisma Schema like so:

1// prisma/schema.prisma
2// ...
3
4model Post {
5 id Int @id @default(autoincrement())
6 title String
7 body String
8 published Boolean
9}

With our database schema specified, we're now ready to proceed to our first database migration! To do that, we'll use the Prisma CLI.

Generate our migration files...

$npx prisma migrate save --experimental

Then, apply the migration...

$npx prisma migrate up --experimental

Access your database

Now let's finally ditch our in-memory data! Let's start by removing the api/db.ts module and then be guided by TypeScript errors.

$rm api/db.ts

In api/app.ts module, remove the db import and the schema.addToContext call.

1// api/app.ts
2
- import { schema, use } from 'nexus'
+ import { use } from 'nexus'
5 import { prisma } from 'nexus-plugin-prisma'
- import { db } from './db'
7
8 use(prisma())
9
- schema.addToContext(() => {
- return {
- db,
- }
- })

You might be wondering how you'll maintain access to the db client in your GraphQL Context given that we've just deleted it. Nexus plugins help here. One of their many capabilities is augmenting your GraphQL context.

In this case, the prisma plugin adds a db property, an instance of Prisma Client, one of the tools in the Prisma toolkit.

Let's now replace all our previous in-memory db interactions with calls to the Prisma Client

1schema.extendType({
2 type: 'Query',
3 definition(t) {
4 t.list.field('drafts', {
5 type: 'Post',
6 resolve(_root, _args, ctx) {
- return ctx.db.posts.filter((p) => p.published === false)
+ return ctx.db.post.findMany({ where: { published: false } })
9 },
10 });
11 t.list.field('posts', {
12 type: 'Post',
13 resolve(_root, _args, ctx) {
- return ctx.db.posts.filter((p) => p.published === true)
+ return ctx.db.post.findMany({ where: { published: true } })
16 },
17 })
18 },
19});
1schema.extendType({
2 type: 'Mutation',
3 definition(t) {
4 t.field('createDraft', {
5 type: 'Post',
6 args: {
7 title: schema.stringArg({ required: true }),
8 body: schema.stringArg({ required: true }),
9 },
10 resolve(_root, args, ctx) {
11 const draft = {
- id: ctx.db.posts.length + 1,
13 title: args.title,
14 body: args.body,
15 published: false,
16 }
- ctx.db.posts.push(draft)
18
- return draft
+ return ctx.db.post.create({ data: draft })
21 },
22 })
23
24 t.field('publish', {
25 type: 'Post',
26 args: {
27 draftId: schema.intArg({ required: true }),
28 },
29 resolve(_root, args, ctx) {
- let postToPublish = ctx.db.posts.find((p) => p.id === args.draftId)
31
- if (!postToPublish) {
- throw new Error('Could not find draft with id ' + args.draftId)
- }
35
- postToPublish.published = true
37
- return postToPublish
39
+ return ctx.db.post.update({
+ where: { id: args.draftId },
+ data: {
+ published: true,
+ },
+ });
46 },
47 })
48 },
49})

If you need a copy & pastable version, here it is

1schema.extendType({
2 type: 'Query',
3 definition(t) {
4 t.list.field('drafts', {
5 type: 'Post',
6 resolve(_root, _args, ctx) {
7 return ctx.db.post.findMany({ where: { published: false } })
8 },
9 })
10 t.list.field('posts', {
11 type: 'Post',
12 resolve(_root, _args, ctx) {
13 return ctx.db.post.findMany({ where: { published: true } })
14 },
15 })
16 },
17})
18
19schema.extendType({
20 type: 'Mutation',
21 definition(t) {
22 t.field('createDraft', {
23 type: 'Post',
24 args: {
25 title: schema.stringArg({ required: true }),
26 body: schema.stringArg({ required: true }),
27 },
28 resolve(_root, args, ctx) {
29 const draft = {
30 title: args.title,
31 body: args.body,
32 published: false,
33 }
34 return ctx.db.post.create({ data: draft })
35 },
36 })
37
38 t.field('publish', {
39 type: 'Post',
40 args: {
41 draftId: schema.intArg({ required: true }),
42 },
43 resolve(_root, args, ctx) {
44 return ctx.db.post.update({
45 where: { id: args.draftId },
46 data: {
47 published: true,
48 },
49 })
50 },
51 })
52 },
53})

Try it out

Awesome, you're ready to open up the playground and create a draft! If all goes well, good job! If not, no worries, there's a lot of integration pieces in this chapter where something could have gone wrong. If after reviewing your steps you still don't understand the issue, feel free to open up a discussion asking for help.

Wrapping up

We've just changed our code, so we must be due or overdue for a test update right? Well, in the next chapter we'll do just that, and show you how Nexus testing works with Prisma.

Next →
Edit this page on Github