Skip to content

Latest commit

 

History

History
405 lines (310 loc) · 9.96 KB

06-chapter-5-persisting-data-via-prisma.mdx

File metadata and controls

405 lines (310 loc) · 9.96 KB
title
5. Persisting data (via Prisma)

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

Connect to your database

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

  • Install the Prisma Client & the Prisma CLI
  • Use it in your api/schema.ts module
  • Create your Prisma Schema
  • Create a .env file to store your database credentials
  • Connect to your database

like so:

npm add @prisma/client && npm add --save-dev @prisma/cli
npx prisma init

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 nexus-tutorial-postgres postgres:10

That's it. You now have a Postgres server running.

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.

<TabbedContent tabs={['Diff', 'Code']}>

-DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
+DATABASE_URL="postgresql://postgres:postgres@localhost:5432/myapp"
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/myapp"

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:

<TabbedContent tabs={['Diff', 'Code']}>

// prisma/schema.prisma

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

+model Post {
+  id        Int     @id @default(autoincrement())
+  title     String
+  body      String
+  published Boolean
+}
// prisma/schema.prisma

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  body      String
  published Boolean
}

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

You'll be asked to create the database (use the default) and give a name to the migration (call it "init").

Then, apply the migration...

npx prisma migrate up --experimental

Access your database

Now let's finally ditch our in-memory data! Let's replace it with the Prisma Client

<TabbedContent tabs={['Diff', 'Code']}>

// api/db.ts
+import { PrismaClient } from '@prisma/client'

export const db = new PrismaClient()

-export interface Post {
-  id: number
-  title: string
-  body: string
-  published: boolean
-}

-export interface Db {
-  posts: Post[]
-}

-export const db: Db = {
-  posts: [{ id: 1, title: 'Nexus', body: '...', published: false }],
-}
// api/db.ts
import { PrismaClient } from '@prisma/client'

export const db = new PrismaClient()

Then, update your Context type so that it uses the PrismaClient created in ./db.ts

<TabbedContent tabs={['Diff', 'Code']}>

// api/context.ts
import { db } from "./db";
+import { PrismaClient } from "@prisma/client"

export interface Context {
-  db: Db
+  db: PrismaClient
}

export const context = {
  db,
}
// api/context.ts
import { db } from "./db";
import { PrismaClient } from "@prisma/client"

export interface Context {
  db: PrismaClient
}

export const context = {
  db,
}

Finally, generate the PrismaClient using the Prisma CLI

npx prisma generate

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

<TabbedContent tabs={['Diff', 'Code']}>

// api/graphql/Post.ts
// ...

export const PostQuery = extendType({
  type: 'Query',
  definition(t) {
    t.list.field('drafts', {
      type: 'Post',
      resolve(_root, _args, ctx) {
-        return ctx.db.posts.filter((p) => p.published === false)
+        return ctx.db.post.findMany({ where: { published: false } })
      },
    });
    t.list.field('posts', {
      type: 'Post',
      resolve(_root, _args, ctx) {
-       return ctx.db.posts.filter((p) => p.published === true)
+       return ctx.db.post.findMany({ where: { published: true } })
      },
    })
  },
})

export const PostMutation = extendType({
  type: 'Mutation',
  definition(t) {
    t.field('createDraft', {
      type: 'Post',
      args: {
        title: nonNull(stringArg()),
        body: nonNull(stringArg()),
      },
      resolve(_root, args, ctx) {
        const draft = {
-         id: ctx.db.posts.length + 1,
          title: args.title,
          body: args.body,
          published: false,
        }
-       ctx.db.posts.push(draft)

-       return draft
+       return ctx.db.post.create({ data: draft })
      },
    })

    t.field('publish', {
      type: 'Post',
      args: {
        draftId: nonNull(intArg()),
      },
      resolve(_root, args, ctx) {
-       let postToPublish = ctx.db.posts.find((p) => p.id === args.draftId)

-       if (!postToPublish) {
-         throw new Error('Could not find draft with id ' + args.draftId)
-       }

-       postToPublish.published = true

-       return postToPublish

+       return ctx.db.post.update({
+         where: { id: args.draftId },
+         data: {
+           published: true,
+         },
+       });
      },
    })
  },
})
// api/graphql/Post.ts
// ...

export const PostQuery = extendType({
  type: 'Query',
  definition(t) {
    t.list.field('drafts', {
      type: 'Post',
      resolve(_root, _args, ctx) {
        return ctx.db.post.findMany({ where: { published: false } })
      },
    })
    t.list.field('posts', {
      type: 'Post',
      resolve(_root, _args, ctx) {
        return ctx.db.post.findMany({ where: { published: true } })
      },
    })
  },
})

export const PostMutation = extendType({
  type: 'Mutation',
  definition(t) {
    t.field('createDraft', {
      type: 'Post',
      args: {
        title: nonNull(stringArg()),
        body: nonNull(stringArg()),
      },
      resolve(_root, args, ctx) {
        const draft = {
          title: args.title,
          body: args.body,
          published: false,
        }
        return ctx.db.post.create({ data: draft })
      },
    })

    t.field('publish', {
      type: 'Post',
      args: {
        draftId: nonNull(intArg()),
      },
      resolve(_root, args, ctx) {
        return ctx.db.post.update({
          where: {
            id: args.draftId
          },
          data: {
            published: true,
          },
        })
      },
    })
  },
})

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.

<ButtonLink color="dark" type="primary" href="/getting-started/tutorial/chapter-testing-with-prisma"

Next →