Build a GraphQL Todolist server on Prisma/PostgreSQL
Tags: graphql, node
Published: 2020-09-09 12:09:06


## [Github](https://github.com/pcreem/graphqlTodoServer) and [Demo](https://prisma--deploy.herokuapp.com/)


## Brief intro about GraphQL and Prisma
[GraphQL](https://graphql.org/) is developed by Facebook in 2015. On the Client side, It makes nest data fetching easier by JSON like interface (as the image above),  rather than multiple URLs or ORM/database request. On the Server side, you can update the data model by add or delete row at the aging field.


[Prisma](https://www.prisma.io/) is an alternative ORM and SQL query builder.


_The following will show how to build a GraphQL backend from scratch._
### prerequisites: 
* [Node.js](https://nodejs.org/en/) installed on your machine
* [PostgreSQL](https://www.postgresql.org/) database server running

### A. setup database: 
`mkdir todo 
mkdir todo/backend 
cd todo/backend
npm init -y 
npm install @prisma/cli - save-dev
npx prisma init`

#### database model:
`code ./prisma/schema.prisma`
```
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

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

model Post {
  id        Int      @default(autoincrement()) @id
  createdAt DateTime @default(now())
  title     String
  content   String? //question mark means opational
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model User {
  id       Int     @default(autoincrement()) @id
  email    String  @unique
  name     String?
  password String
  posts    Post[]
}

```

`code ./prisma/.env`
```
DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE
```

#### Prisma Migrate:
`npx prisma migrate save --name init --experimental 
npx prisma migrate up --experimental
npx prisma generate`

### B. Build server
`npm i graphql-yoga @prisma/client 
code index.js
`
```
const { PrismaClient } = require('@prisma/client')
const prisma = new PrismaClient()
const { GraphQLServer } = require('graphql-yoga')

const Query = require('./resolvers/query.js')
const Mutation = require('./resolvers/mutation.js')
const User = require('./resolvers/user.js')
const Post = require('./resolvers/post.js')

const resolvers = {
  Query,
  Mutation,
  User,
  Post
}

const server = new GraphQLServer({
  typeDefs: './schema.graphql',
  resolvers,
  context: request => {
    return {
      ...request,
      prisma,
    }
  },
})

const PORT = process.env.PORT || 4000
server.start(PORT, () => console.log(`Server is running on http://localhost:4000`))
```

1. The `typeDefs` connect to the schema wrote at the t section. It defined the database path, data type,  and the relation between them.
2. The `resolvers` defined how to deal with data in each relevant script. For example, Query responsible for fetching data, Mutation for CRUD/ Signup/Login function, we will deal with it later. The rest two (User and Post in resolvers field) define data relations. 
3. The `context` contains custom data being passed through your resolver chain.

### C. Build typeDefs, resolvers, and user identification
* define typeDefs
`code schema.graphql` 
```
type Query {
  info: [Post!]!
}

type Mutation {
  upsertPost (postId:ID!, title: String!, content: String): Post!
  deletePost (postId:ID!): Post

  signup(email: String!, password: String!, name: String!): AuthPayload
  login(email: String!, password: String!): AuthPayload
}

type Post {
  id: ID!
  title: String!
  content: String
  author:    User
  createdAt: String!
}

type AuthPayload {
  token: String
  user: User
}

type User {
  id: ID!
  name: String
  email: String!
  posts: [Post]
}
```

* define resolvers

`npm i jsonwebtoken bcryptjs dotenv`
`mkdir resolvers`
`code ./resolvers/query.js`

```
const { getUserId } = require('../utils')

function info(parent, args, context, info) {
  const userId = getUserId(context)
  const Posts = context.prisma.post.findMany({
    where: {
      authorId: parseInt(userId)
    }
  })

  return Posts
}


module.exports = {
  info
}
```

`code ./resolvers/mutation.js`
```
const { getUserId } = require('../utils')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
require('dotenv').config()
const APP_SECRET = process.env.APP_SECRET

function upsertPost(parent, args, context, info) {
  const userId = getUserId(context)
  const upsertPost = context.prisma.post.upsert({
    where: {
      id: parseInt(args.postId)
    },
    update: {
      title: args.title,
      content: args.content,
    },
    create: {
      title: args.title,
      content: args.content,
      author: {
        connect: { id: parseInt(userId) },
      },
    },
  })

  return upsertPost
}


function deletePost(parent, args, context, info) {
  const deletePost = context.prisma.post.delete({

    where: {
      id: parseInt(args.postId),
    },
  })

  return deletePost
}

async function signup(parent, args, context, info) {
  const password = await bcrypt.hash(args.password, 10)
  const user = await context.prisma.user.create({ data: { ...args, password } })
  const token = jwt.sign({ userId: user.id }, APP_SECRET)

  return {
    token,
    user,
  }
}

async function login(parent, args, context, info) {
  const user = await context.prisma.user.findOne({ where: { email: args.email } })
  if (!user) {
    throw new Error('No such user found')
  }
  const valid = await bcrypt.compare(args.password, user.password)
  if (!valid) {
    throw new Error('Invalid password')
  }
  const token = jwt.sign({ userId: user.id }, APP_SECRET)

  return {
    token,
    user,
  }
}

module.exports = {
  upsertPost,
  deletePost,

  signup,
  login,
}
```

`code ./resolvers/user.js`
```
function posts(parent, args, context) {
  return context.prisma.user.findOne({ where: { id: parent.id } }).posts()
}

module.exports = {
  posts,
}
```

`code ./resolvers/post.js`
```
function author(parent, args, context) {
  return context.prisma.post.findOne({ where: { id: parent.id } }).author()
}

module.exports = {
  author,
}
```

The four scrips function have short explained on the above second item (The resolvers).

* Build a helper to handle user identification
`code utils.js`
```
const jwt = require('jsonwebtoken')
require('dotenv').config()
const APP_SECRET = process.env.APP_SECRET

function getUserId(context) {
  const Authorization = context.request.get('Authorization')
  if (Authorization) {
    const token = Authorization.replace('Bearer ', '')
    const { userId } = jwt.verify(token, APP_SECRET)
    return userId
  }

  throw new Error('Not authenticated')
}

module.exports = {
  getUserId,
}
```

`code .env`
```
APP_SECRET = Your_APP_SECRET
```

### D. Start GraphQL server
`node index.js`

you could try the following command on GraphQL left column
* _signup_
```
mutation {
  signup(
    name: "__yourname__"
    email: "__your@email.com__"
    password: "__yourpassword__"
  ) {
    token
    user {
      id
    }
  }
}
```


* _login_
```
mutation {
  login(
    email: "__your@email.com__"
    password: "__yourpassword__"
  ) {
    token
    user {
      id
      name
      posts{
        id
        title
      }
    }
  }
}
```

* _add todo_

a. copy token to Header(bottom left)

```
{ "Authorization": "Bearer __Token__" }
```
b. command
```
mutation {
  upsertPost(
    postId:"0"
    title: "www.graphqlconf.org"
  ) {
    id
    titile
  }
}
```

* _delete todo_
```
mutation {
  deletePost(
    postId:"__Id__" //enter todo id
  ) {
    id
  }
}
```