[go: up one dir, main page]

100% found this document useful (1 vote)
441 views117 pages

2.1 PDF-Reference-The-Modern-GraphQL-Bootcamp PDF

The document demonstrates how to build a GraphQL API with GraphQL-Yoga. It shows: 1) Defining the schema with object types like Query and fields. 2) Implementing resolvers to provide data for fields on types like Query. 3) Making requests to the GraphQL API to retrieve data by querying fields on types. It progresses from simple queries on Query to more complex examples involving nested objects, arguments, and fetching related data through references. This provides a concise overview of building a basic GraphQL API with GraphQL-Yoga.

Uploaded by

Jaswant Singh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
441 views117 pages

2.1 PDF-Reference-The-Modern-GraphQL-Bootcamp PDF

The document demonstrates how to build a GraphQL API with GraphQL-Yoga. It shows: 1) Defining the schema with object types like Query and fields. 2) Implementing resolvers to provide data for fields on types like Query. 3) Making requests to the GraphQL API to retrieve data by querying fields on types. It progresses from simple queries on Query to more complex examples involving nested objects, arguments, and fetching related data through references. This provides a concise overview of building a basic GraphQL API with GraphQL-Yoga.

Uploaded by

Jaswant Singh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 117

2

3
4
5
6
query {

course

query {
course
}

{
"data": {
"course": "GraphQL"
}
}

7
query {
course
me
}

8
query {
course
me {
id
name
email
}
}

{
"data": {
"course": "GraphQL",
"me": {
"id": "affb67e1-0d3b-40ae-b58d-7935a5506012",
"name": "Andrew Mead",
"email": "andrew@example.com"
}
}
}

User

9
query {
users {
id
name
}
}

{
"data": {
"users": [
{
"id": "affb67e1-0d3b-40ae-b58d-7935a5506012",
"name": "Andrew"
},
{
"id": "c2d96c35-e28a-4bfd-b075-3427bc09426a",
"name": "Sarah"
},
{
"id": "8b457570-06fa-4195-a712-d24bbc1b5294",
"name": "Michael"
}
]
}
}

10
babel-cli babel-preset-env

npm install babel-cli@6.26.0 babel-preset-env@1.7.0

.babelrc
babel-preset-env

{
"presets": [
"env"
]
}

{
"scripts": {
"start": "babel-node src/index.js"
}
}

npm run start

11
export

// utilities.js
const add = (a, b) => a + b
const subtract = (a, b) => a - b

export { add, subtract }

import

// index.js
import { add, subtract } from './utilities'

console.log(add(32, 1)) // Will print: 33


console.log(subtractadd(32, 1)) // Will print: 31

add subtract
square

12
// utilities.js
const add = (a, b) => a + b
const subtract = (a, b) => a - b
const square = (x) => x * x

export { add, subtract, square as default }

square otherSquare
add

// index.js
import otherSquare, { add } from './utilities'

console.log(add(32, 1)) // Will print: 33


console.log(otherSquare(10)) // Will print: 100

graphql-yoga

13
npm install graphql-yoga@1.14.10

graphql-yoga

import { GraphQLServer } from 'graphql-yoga'

// Type definitions (schema)


const typeDefs = `
type Query {
hello: String!
}
`

// Resolvers
const resolvers = {
Query: {
hello() {
return 'This is my first query!'
}
}
}

const server = new GraphQLServer({


typeDefs,
resolvers
})

server.start(() => {
console.log('The server is up!')
})

typeDefs

Query

Query

14
typeDefs
resolvers
typeDefs resolvers Query
Query hello hello

graphql-yoga
localhost:4000

query {
hello
}

15
{
"data": {
"hello": "This is my first query!"
}
}

type Query {
title: String!
price: Float!
releaseYear: Int
rating: Float
inStock: Boolean!
}

16
const resolvers = {
Query: {
title() {
return 'The War of Art'
},
price() {
return 12.99
},
releaseYear() {
return null
},
rating() {
return 5
},
inStock() {
return true
}
}
}

rating Float price Float!


! !

rating null
price price

17
npm install nodemon@1.17.5 --save-dev

{
"scripts": {
"start": "nodemon src/index.js --exec babel-node"
}
}

typeDefs

type Product {

id

18
type Product {
id: ID!
title: String!
price: Float!
releaseYear: Int
rating: Float
inStock: Boolean!
}

product

type Query {
product: Product!
}

product Product! !
User
product

const resolvers = {
Query: {
product() {
return {
id: '123',
title: 'Watch',
price: 39.99,
rating: 4.8,
inStock: false
}
}
}
}

19
query {
product {
id
title
rating
}
}

{
"data": {
"product": {
"id": "123",
"title": "Watch",
"rating": 4.8
}
}
}

20
greeting

type Query {
greeting(name: String): String!
}

typeDefs greeting

"args"

const resolvers = {
Query: {
greeting(parent, args, ctx, info) {
if (args.name) {
return `Hello, ${args.name}!`
} else {
return 'Hello!'
}
}
}
}

name String
String!

type Query {
greeting(name: String!): String!
}

name
"Andrew" name

21
query {
greeting(name: "Andrew")
}

{
"data": {
"product": "Hello, Andrew!"
}
}

grades
[Int!]!

type Query {
grades: [Int!]!
}

[]!

Int!

grades

22
const resolvers = {
Query: {
grades() {
return [
99,
74,
100,
3
]
}
}
}

query {
grades
}

{
"data": {
"grades": [99, 74, 100, 3]
}
}

users
User

23
type Query {
users: [User!]!
}

type User {
id: ID!
name: String!
age: Int
}

User id name
age

const resolvers = {
Query: {
users() {
return [{
id: '1',
name: 'Jamie'
}, {
id: '2',
name: 'Andrew',
age: 27
}, {
id: '3',
name: 'Katie'
}]
}
}
}

id

24
query {
users {
id
}
}

query {
users {
id
name
age
}
}

25
{
"data": {
"users": [
{
"id": "1",
"name": "Jamie",
"age": null
},
{
"id": "2",
"name": "Andrew",
"age": 27
},
{
"id": "3",
"name": "Katie",
age: null
}
]
}
}

User Post author

26
type User {
id: ID!
name: String!
email: String!
age: Int
}

type Post {
id: ID!
title: String!
body: String!
published: Boolean!
author: User!
}

author
Post

parent
parent.author

const resolvers = {
// Query object hidden for brevity
Post: {
author(parent, args, ctx, info) {
return users.find((user) => {
return user.id === parent.author
})
}
}
}

id
title id title
author Post

27
query {
posts {
id
title
author {
name
}
}
}

posts User
[Post!]!

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

type Post {
id: ID!
title: String!
body: String!
published: Boolean!
author: User!
}

parent

28
const resolvers = {
// Query and Post objects hidden for brevity
User: {
posts(parent, args, ctx, info) {
return posts.filter((post) => {
return post.author === parent.id
})
}
}
}

posts

id title

query {
users {
id
name
posts {
id
title
}
}
}

29
Query
Mutation

createUser
name email
age
User

type Mutation {
createUser(name: String!, email: String!, age: Int): User!
}

Mutation resolvers createUser


createUser

30
import uuidv4 from 'uuid/v4'

const resolvers = {
// Other properties hidden for brevity
Mutation: {
createUser(parent, args, ctx, info) {
const emailTaken = users.some((user) => user.email ===
args.email)

if (emailTaken) {
throw new Error('Email taken')
}

const user = {
id: uuidv4(),
name: args.name,
email: args.email,
age: args.age
}

users.push(user)

return user
}
}
}

mutation
createUser name email
User

31
mutation {
createUser(name: "Andrew", email: "testing@example.com"){
id
name
email
age
}
}

{
"data": {
"createUser": {
"id": "8257f14f-80b0-4313-ad35-9047e3b5f851",
"name": "Andrew",
"email": "testing2@example.com",
"age": null
}
}
}

createUser

32
{
"data": null,
"errors": [
{
"message": "Email taken",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createUser"
]
}
]
}

title body published


author author

type Mutation {
createPost(title: String!, body: String!, published: Boolean!,
author: ID!): Post!
}

createPost

posts

33
const resolvers = {
// Other properties hidden for brevity
Mutation: {
createPost(parent, args, ctx, info) {
const userExists = users.some((user) => user.id === args.author)

if (!userExists) {
throw new Error('User not found')
}

const post = {
id: uuidv4(),
title: args.title,
body: args.body,
published: args.published,
author: args.author
}

posts.push(post)

return post
}
}
}

createPost
"2"

34
mutation {
createPost(
title:"My new post",
body:"",
published:false,
author:"2"
){
id
title
body
published
author {
name
}
comments {
id
}
}
}

{
"data": {
"createPost": {
"id": "18735d13-6b96-4eba-9b85-81da86853231",
"title": "My new post",
"body": "",
"published": false,
"author": {
"name": "Sarah"
},
"comments": []
}
}
}

35
npm install babel-plugin-transform-object-rest-spread@6.26.0

.babelrc plugins

{
"presets": [
"env"
],
"plugins": [
"transform-object-rest-spread"
]
}

one two one name age


two age location two one
...one two
age location name

36
const one = {
name: 'Andrew',
age: 27
}

const two = {
age: 25,
location: 'Philadelphia',
...one
}

console.log(two)

// Will print:
// {
// age: 27,
// location: "Philadelphia",
// name: "Andrew"
// }

createUser
name email age
data

37
type Mutation {
createUser(data: CreateUserInput!): User!
}

input CreateUserInput {
name: String!
email: String!
age: Int
}

createUser args.data

createUser(parent, args, ctx, info) {


// Can access name, email, and age on args.data
},

createUser
data
CreateUserInput

mutation {
createUser(
data:{
name:"Jess",
email:"jess@example.com"
}
){
id
}
}

38
deleteUser

deleteUser id

type Mutation {
deleteUser(id: ID!): User!
}

39
const resolvers = {
// Other properties hidden for brevity
Mutation: {
deleteUser(parent, args, ctx, info) {
const userIndex = users.findIndex((user) => user.id === args.id)

if (userIndex === -1) {


throw new Error('User not found')
}

const deletedUsers = users.splice(userIndex, 1)

posts = posts.filter((post) => {


const match = post.author === args.id

if (match) {
comments = comments.filter((comment) => comment.post !==
post.id)
}

return !match
})
comments = comments.filter((comment) => comment.author !==
args.id)

return deletedUsers[0]
}
}
}

index.js

40
ctx

db

const server = new GraphQLServer({


typeDefs: './src/schema.graphql',
resolvers,
context: {
db
}
})

users
db db.users

const resolvers = {
// Other properties hidden for brevity
Query: {
users(parent, args, { db }, info) {
if (!args.query) {
return db.users
}

return db.users.filter((user) => {


return
user.name.toLowerCase().includes(args.query.toLowerCase())
})
},
}
}

41
updateUser id
data
UpdateUserInput

type Mutation {
updateUser(id: ID!, data: UpdateUserInput!): User!
}

input UpdateUserInput {
name: String
email: String
age: Int
}

deleteUser updateUser
data

42
updateUser(parent, args, { db }, info) {
const { id, data } = args
const user = db.users.find((user) => user.id === id)

if (!user) {
throw new Error('User not found')
}

if (typeof data.email === 'string') {


const emailTaken = db.users.some((user) => user.email ===
data.email)

if (emailTaken) {
throw new Error('Email taken')
}

user.email = data.email
}

if (typeof data.name === 'string') {


user.name = data.name
}

if (typeof data.age !== 'undefined') {


user.age = data.age
}

return user
},

43
PubSub graphql-
yoga
PubSub context

44
import { GraphQLServer, PubSub } from 'graphql-yoga'
import db from './db'
import Query from './resolvers/Query'
import Mutation from './resolvers/Mutation'
import Subscription from './resolvers/Subscription'
import User from './resolvers/User'
import Post from './resolvers/Post'
import Comment from './resolvers/Comment'

const pubsub = new PubSub()

const server = new GraphQLServer({


typeDefs: './src/schema.graphql',
resolvers: {
Query,
Mutation,
Subscription,
User,
Post,
Comment
},
context: {
db,
pubsub
}
})

server.start(() => {
console.log('The server is up!')
})

Subscription
count

45
type Subscription {
count: Int!
}

Subscription count
count subscribe count

pubsub.asyncIterator
pubsub.publish pubsub.asyncIterator
pubsub.publish

const Subscription = {
count: {
subscribe(parent, args, { pubsub }, info) {
let count = 0

setInterval(() => {
count++
pubsub.publish('count', {
count
})
}, 1000)

return pubsub.asyncIterator('count')
}
}
}

export { Subscription as default }

count

46
subscription {
count
}

{
"data": {
"count": 1
}
}

{
"data": {
"count": 2
}
}

{
"data": {
"count": 3
}
}

comment
postId

47
type Subscription {
comment(postId: ID!): Comment!
}

comment
postId

postId 1 comment 1

const Subscription = {
comment: {
subscribe(parent, { postId }, { db, pubsub }, info){
const post = db.posts.find((post) => post.id === postId &&
post.published)

if (!post) {
throw new Error('Post not found')
}

return pubsub.asyncIterator(`comment ${postId}`)


}
}
}

export { Subscription as default }

pubsub.publish
pubsub.publish createComment

48
const Mutation = {
createComment(parent, args, { db, pubsub }, info) {
const userExists = db.users.some((user) => user.id ===
args.data.author)
const postExists = db.posts.some((post) => post.id === args.data.post
&& post.published)

if (!userExists || !postExists) {
throw new Error('Unable to find user and post')
}

const comment = {
id: uuidv4(),
...args.data
}

db.comments.push(comment)
pubsub.publish(`comment ${args.data.post}`, { comment })

return comment
}
}

11

subscription {
comment(postId:"11"){
id
text
author{
id
name
}
}
}

49
{
"data": {
"comment": {
"id": "f6925dbb-8899-4be5-9ab6-365e698931c2",
"text": "My new comment",
"author": {
"id": "1",
"name": "Andrew"
}
}
}
}

data
mutation mutation
"CREATED" "UPDATED" "DELETED"

50
type Subscription {
post: PostSubscriptionPayload!
}

type PostSubscriptionPayload {
mutation: String!
data: Post!
}

pubsub.publish
data mutation
publish

const Mutation = {
deletePost(parent, args, { db, pubsub }, info) {
const postIndex = db.posts.findIndex((post) => post.id === args.id)

if (postIndex === -1) {


throw new Error('Post not found')
}

const [post] = db.posts.splice(postIndex, 1)

db.comments = db.comments.filter((comment) => comment.post !==


args.id)

if (post.published) {
pubsub.publish('post', {
post: {
mutation: 'DELETED',
data: post
}
})
}

return post
}
}

51
subscription {
post{
mutation
data {
id
title
body
author{
id
name
}
}
}
}

mutation

{
"data": {
"post": {
"mutation": "UPDATED",
"data": {
"id": "10",
"title": "GraphQL 101",
"body": "Something new...",
"author": {
"id": "1",
"name": "Andrew"
}
}
}
}
}

52
PowerState on off asleep

enum PowerState {
on
off
asleep
}

PowerState

mutation mutation
CREATED UPDATED DELETED

53
enum MutationType {
CREATED
UPDATED
DELETED
}

type PostSubscriptionPayload {
mutation: MutationType!
data: Post!
}

type CommentSubscriptionPayload {
mutation: MutationType!
data: Comment!
}

54
55
npm i -g prisma@1.12.0

prisma init prisma init

prisma init prisma

prisma init

prisma init

56
cd prisma

docker-compose up -d

prisma deploy

ssl true
docker-compose.yml

datamodel.graphql

type User {
id: ID! @unique
name: String!
}

User
createUser users

57
mutation {
createUser(
data:{
name:"Vikram"
}
){
id
name
}
}

{
"data": {
"createUser": {
"id": "cjjsq2rqf001d0822g2if54em",
"name": "Vikram"
}
}
}

datamodel.graphql

58
type User {
id: ID! @unique
name: String!
email: String! @unique
posts: [Post!]!
}

type Post {
id: ID! @unique
title: String!
body: String!
published: Boolean!
author: User!
}

datamodel.graphql
prisma deploy

59
mutation {
createPost(
data:{
title:"Prisma post",
body:"",
published:false,
author:{
connect:{
id:"cjjucbpfg004i0822onkb9z2l"
}
}
}
){
id
title
body
published
author{
id
name
}
}
}

60
{
"data": {
"createPost": {
"author": {
"id": "cjjucbpfg004i0822onkb9z2l",
"name": "Vikram"
},
"body": "",
"published": false,
"id": "cjjud0s7200580822gywi3e7y",
"title": "Prisma post"
}
}
}

prisma-binding
prisma-binding
graphql-cli

npm install prisma-binding@2.1.1 graphql-cli@2.16.4

prisma-binding
graphql get-schema graphql-cli

.graphqlconfig

61
{
"projects": {
"prisma": {
"schemaPath": "src/generated/prisma.graphql",
"extensions": {
"endpoints": {
"default": "http://localhost:4466"
}
}
}
}
}

scripts package.json

{
"scripts": {
"get-schema": "graphql get-schema -p prisma"
}
}

npm run get-schema prisma-binding

// prisma.js
import { Prisma } from 'prisma-binding'

const prisma = new Prisma({


typeDefs: 'src/generated/prisma.graphql',
endpoint: 'localhost:4466'
})

62
datamodel.graphql

prisma.query users comments


users comments
prisma.query

prisma.query.users(null, '{ id name posts { id title } }').then((data) => {


console.log(JSON.stringify(data, undefined, 2))
})

prisma.query.comments(null, '{ id text author { id name } }').then((data) =>


{
console.log(JSON.stringify(data, undefined, 2))
})

prisma.mutation prisma.query
prisma.mutation
deletePost
prisma.mutation.deletePost

createPost

63
prisma.mutation.createPost({
data: {
title: "GraphQL 101",
body: "",
published: false,
author: {
connect: {
id: "cjjybkwx5006h0822n32vw7dj"
}
}
}
}, '{ id title body published }').then((data) => {
console.log(data)
})

createPostForUser

prisma.mutation.createPost
prisma.query.use

64
const createPostForUser = async (authorId, data) => {
const post = await prisma.mutation.createPost({
data: {
...data,
author: {
connect: {
id: authorId
}
}
}
}, '{ id }')
const user = await prisma.query.user({
where: {
id: authorId
}
}, '{ id name email posts { id title published } }')
return user
}

createPostForUser

createPostForUser('cjjucl3yu004x0822dq5tipuz', {
title: 'Great books to read',
body: 'The War of Art',
published: true
}).then((user) => {
console.log(JSON.stringify(user, undefined, 2))
})

prisma prisma.exists
User Post
datamodel.graphql User Post prisma.exists

updatePostForUser
prisma.exists.Post

65
const updatePostForUser = async (postId, data) => {
const postExists = await prisma.exists.Post({ id: postId })

if (!postExists) {
throw new Error('Post not found')
}

const post = await prisma.mutation.updatePost({


where: {
id: postId
},
data
}, '{ author { id name email posts { id title published } } }')

return post.author
}

updatePostForUser catch

updatePostForUser("power", { published: true }).then((user) => {


console.log(JSON.stringify(user, undefined, 2))
}).catch((error) => {
console.log(error.message)
})

datamodel.graphl
unique relation

relation

66
onDelete CASCASE

onDelete SET_NULL

type Job {
id: ID! @unique
title: String!
qualifications: String!
salary: Int!
applicants: [Applicant!]! @relation(name: "ApplicantToJob",
onDelete: CASCADE)
}

type Applicant {
id: ID! @unique
name: String!
email: String! @unique
resume: String!
job: Job! @relation(name: "ApplicantToJob", onDelete: SET_NULL)
}

67
prisma-binding

import { Prisma } from 'prisma-binding'

const prisma = new Prisma({


typeDefs: 'src/generated/prisma.graphql',
endpoint: 'http://localhost:4466'
})

export { prisma as default }

prisma.query
prisma.mutation prisma.subscription prisma.exists

users prisma.query.users

68
const Query = {
users(parent, args, { prisma }, info) {
return prisma.query.users(null, info)
}
}

prisma-binding

undefined

info

users query

69
const Query = {
users(parent, args, { prisma }, info) {
const opArgs = {}

if (args.query) {
opArgs.where = {
OR: [{
name_contains: args.query
}, {
email_contains: args.query
}]
}
}

return prisma.query.users(opArgs, info)


}
}

"Andrew"

query {
users (
query:"Andrew"
) {
id
name
email
}
}

70
query {
users {
id
name
email
posts {
id
title
}
}
}

User.posts User.comments
Post.author Post.comments
Comment.author Comment.post

const User = {
posts(parent, args, { db }, info) {
return db.posts.filter((post) => {
return post.author === parent.id
})
},
comments(parent, args, { db }, info) {
return db.comments.filter((comment) => {
return comment.author === parent.id
})
}
}

export { User as default }

info prisma-binding info

User.js Comment.js Post.js

71
const User = {

export { User as default }

prisma-binding
prisma.query
prisma.exists prisma.mutation createUser

const Mutation = {
async createUser(parent, args, { prisma }, info) {
const emailTaken = await prisma.exists.User({ email: args.data.email
})

if (emailTaken) {
throw new Error('Email taken')
}

return prisma.mutation.createUser({ data: args.data }, info)


}
}

prisma.mutation.createUser info

72
pubsub.publish

pubsub.publish

pubsub.publish

prisma.subscription.comment
where

const Subscription = {
comment: {
subscribe(parent, { postId }, { prisma }, info){
return prisma.subscription.comment({
where: {
node: {
post: {
id: postId
}
}
}
}, info)
}
}
}

73
type CommentSubscriptionPayload {
mutation: MutationType!
node: Comment
}

prisma.yml

endpoint: http://localhost:4466
datamodel: datamodel.graphql
secret: thisismysupersecrettext

prisma deploy

secret prisma-binding

74
import { Prisma } from 'prisma-binding'

const prisma = new Prisma({


typeDefs: 'src/generated/prisma.graphql',
endpoint: 'http://localhost:4466',
secret: 'thisismysupersecrettext'
})

export { prisma as default }

prisma token

{
"Authorization":"Bearer YourTokenHere"
}

npm run get-schema get-schema


http://localhost:4466

npm run get-schema prisma


extensions get-schema

75
{
"projects": {
"prisma": {
"schemaPath": "src/generated/prisma.graphql",
"extensions": {
"prisma": "prisma/prisma.yml",
"endpoints": {
"default": "http://localhost:4466"
}
}
}
}
}

get-schema package.json

graphql get-schema -p prisma

createUser
schema.graphql

createUser

76
import bcrypt from 'bcryptjs'

const Mutation = {
async createUser(parent, args, { prisma }, info) {
if (args.data.password.length < 8) {
throw new Error('Password must be 8 characters or longer.')
}

const password = await bcrypt.hash(args.data.password, 10)

return prisma.mutation.createUser({
data: {
...args.data,
password
}
}, info)
}
}

createUser

77
import jwt from 'jsonwebtoken'

const Mutation = {
async createUser(parent, args, { prisma }, info) {
if (args.data.password.length < 8) {
throw new Error('Password must be 8 characters or longer.')
}

const password = await bcrypt.hash(args.data.password, 10)


const user = await prisma.mutation.createUser({
data: {
...args.data,
password
}
})

return {
user,
token: jwt.sign({ userId: user.id }, 'thisisasecret')
}
}
}

78
{
"Authorization": "Bearer
eyJhbGciaiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjamtqczMBcGYwMDQ3MDg3MzJt
NHJoaWFjIiwiaWF0IjoxNTMzNjU2NzE2fQ.MHSey8h1xeIfUmMvV75Vhmzvbx7R7jR65ZWr--
r2oVY"
}

context

const server = new GraphQLServer({


typeDefs: './src/schema.graphql',
resolvers: {
Query,
Mutation,
Subscription,
User,
Post,
Comment
},
context(request) {
return {
db,
pubsub,
prisma,
request
}
}
})

request
createPost

request getUserId getUserId

79
getUserId

const Mutation = {
createPost(parent, args, { prisma, request }, info) {
const userId = getUserId(request)

return prisma.mutation.createPost({
data: {
title: args.data.title,
body: args.data.body,
published: args.data.published,
author: {
connect: {
id: userId
}
}
}
}, info)
}
}

getUserId

import jwt from 'jsonwebtoken'

const getUserId = (request) => {


const header = request.request.headers.authorization

if (!header) {
throw new Error('Authentication required')
}

const token = header.replace('Bearer ', '')


const decoded = jwt.verify(token, 'thisisasecret')

return decoded.userId
}

export { getUserId as default }

80
deletePost
getUserId

const Mutation = {
async deletePost(parent, args, { prisma, request }, info) {
const userId = getUserId(request)
const postExists = await prisma.exists.Post({
id: args.id,
author: {
id: userId
}
})

if (!postExists) {
throw new Error('Unable to delete post')
}

return prisma.mutation.deletePost({
where: {
id: args.id
}
}, info)
}
}

81
post

getUserId

null

const getUserId = (request, requireAuth = true) => {


const header = request.request.headers.authorization

if (header) {
const token = header.replace('Bearer ', '')
const decoded = jwt.verify(token, 'thisisasecret')
return decoded.userId
}

if (requireAuth) {
throw new Error('Authentication required')
}

return null
}

post
getUserId false
requireAuth userId
userId null

prisma.query.posts

82
const Mutation = {
async post(parent, args, { prisma, request }, info) {
const userId = getUserId(request, false)

const posts = await prisma.query.posts({


where: {
id: args.id,
OR: [{
published: true
}, {
author: {
id: userId
}
}]
}
}, info)

if (posts.length === 0) {
throw new Error('Post not found')
}

return posts[0]
}
}

schema.graphql
String! String

User.email

83
getUserId requireAuth false

null

import getUserId from '../utils/getUserId'

const User = {
email(parent, args, { request }, info) {
const userId = getUserId(request, false)

if (userId && userId === parent.id) {


return parent.email
} else {
return null
}
}
}

export { User as default }

userFields
User User
userFields

users

84
query {
users {
...userFields
posts{
id
title
published
}
}
}

fragment userFields on User {


id
name
email
}

85
{
"data": {
"users": [
{
"id": "cjkjs28fg003x0873dqzy26y8",
"name": "Jess",
"email": null,
"posts": []
},
{
"id": "cjkl8qxky005z0873eix4o03n",
"name": "Andrew",
"email": null,
"posts": [
{
"id": "cjkl8rfiz00630873lz1zv2ho",
"title": "Updated post by Andrew",
"published": true
}
]
}
]
}
}

extractFragmentReplacements prisma-binding

fragmentReplacements Prisma GraphQLServer


fragmentReplacements
extractFragmentReplacements

86
import getUserId from '../utils/getUserId'

const User = {
email: {
fragment: 'fragment userId on User { id }',
resolve(parent, args, { request }, info) {
const userId = getUserId(request, false)

if (userId && userId === parent.id) {


return parent.email
} else {
return null
}
}
}
}

export { User as default }

getUserId
getUserId

87
const getUserId = (request, requireAuth = true) => {
const header = request.request ? request.request.headers.authorization :
request.connection.context.Authorization

// The rest of the function is the same and has been removed for brevity
}

getUserId
myPost

const Subscription = {
myPost: {
subscribe(parent, args, { prisma, request }, info) {
const userId = getUserId(request)

return prisma.subscription.post({
where: {
node: {
author: {
id: userId
}
}
}
}, info)
}
}
}

jwt.sign
expiresIn

88
jwt.sign({ userId }, 'thisisasecret', { expiresIn: '7 days' })

jwt.verify

first skip first


first 10 skip

first 10 skip 0
first 10
skip 10

89
first skip

type Query {
users(query: String, first: Int, skip: Int): [User!]!
}

first skip

const Query = {
users(parent, args, { prisma }, info) {
const opArgs = {
first: args.first,
skip: args.skip
}

if (args.query) {
opArgs.where = {
OR: [{
name_contains: args.query
}]
}
}

return prisma.query.users(opArgs, info)


}
}

90
query {
users(first:3, skip:6){
id
name
}
}

after after

first 50 after
after after
first 50 after
'somerandomid'

after

91
type Query {
users(query: String, first: Int, skip: Int, after: String):
[User!]!
}

const Query = {
users(parent, args, { prisma }, info) {
const opArgs = {
first: args.first,
skip: args.skip,
after: args.after
}

if (args.query) {
opArgs.where = {
OR: [{
name_contains: args.query
}]
}
}

return prisma.query.users(opArgs, info)


}
}

id updatedAt createdAt updatedAt


createdAt
DateTime!

92
type Comment {
id: ID! @unique
text: String!
author: User! @relation(name: "CommentToUser", onDelete: SET_NULL)
post: Post!@relation(name: "CommentToPost", onDelete: SET_NULL)
updatedAt: DateTime!
createdAt: DateTime!
}

schema.graphql
String!

type Comment {
id: ID!
text: String!
author: User!
post: Post!
updatedAt: String!
createdAt: String!
}

createdAt updatedAt

query {
comments{
id
text
updatedAt
createdAt
}
}

id text

93
{
"data": {
"comments": [
{
"id": "cjklaufyx009h0873rhadqtpz",
"text": "Jess Comment 3",
"updatedAt": "2018-08-08T15:39:30.881Z",
"createdAt": "2018-08-08T15:39:30.881Z"
},
{
"id": "cjklaugv5009n087301y9ybs2",
"text": "This is updated with auth",
"updatedAt": "2018-08-08T15:42:57.661Z",
"createdAt": "2018-08-08T15:39:31.953Z"
}
]
}
}

orderBy

orderBy

users orderBy
UserOrderByInput

94
# import UserOrderByInput from './generated/prisma.graphql'

type Query {
users(query: String, first: Int, skip: Int, after: String,
orderBy: UserOrderByInput): [User!]!
}

if (args.query) {
opArgs.where = {
OR: [{
name_contains: args.query
}]
}
}

return prisma.query.users(opArgs, info)


}

orderBy createdAt_DESC

95
query {
users(orderBy:createdAt_DESC){
id
name
email
updatedAt
createdAt
}
}

96
prisma.yml

http://localhost:4466

prisma.yml
config dev.env prod.env
dev.env

PRISMA_ENDPOINT=http://localhost:4466

prisma.yml

endpoint: ${env:PRISMA_ENDPOINT}
datamodel: datamodel.graphql
secret: thisismysupersecrettext

prisma deploy

prisma deploy -e ../config/dev.env

97
prisma login

prod.env dev.env

prisma deploy -e ../config/prod.env

prod.env

prisma.yml
prod.env

PRISMA_ENDPOINT=PUT-YOUR-PRODUCTION-URL-HERE

endpoint: ${env:PRISMA_ENDPOINT}
datamodel: datamodel.graphql
secret: thisismysupersecrettext

98
heroku login

PORT 4000

server.start({ port: process.env.PORT || 4000 }, () => {


console.log('The server is up!')
})

prisma.js
http://localhost:4466 PRISMA_ENDPOINT

const prisma = new Prisma({


typeDefs: 'src/generated/prisma.graphql',
endpoint: process.env.PRISMA_ENDPOINT,
secret: 'thisismysupersecrettext',
fragmentReplacements
})

PRISMA_ENDPOINT
env-cmd

npm install env-cmd@8.0.2

dev
dev.env

99
env-cmd ./config/dev.env nodemon src/index.js --ext js,graphql --exec babel-
node

package.json
heroku-postbuild start

package.json

{
"scripts": {
"start": "node dist/index.js",
"heroku-postbuild": "babel src --out-dir dist --copy-files",
}
}

babel-node babel

npm install @babel/polyfill@7.0.0

src/index.js

100
import '@babel/polyfill/noConflict'

node_modules

config
.gitignore

node_modules/
config/

git init

git add

git add .

git commit -am "Init commit"

101
heroku create

git push heroku master

config
test.env default
test

102
PRISMA_ENDPOINT=http://localhost:4466/default/test
PRISMA_SECRET=pi389xjam2b3pjsd0
JWT_SECRET=23oijdsm23809sdf

prisma

prisma deploy -e ../config/test.env

jest

npm install jest@23.5.0

{
"scripts": {
"test": "jest --watch"
}
}

.test.js tests

user.test.js tests test

103
test('Should create a new user', () => {

})

test

isValidPassword

const isValidPassword = (password) => {


return password.length >= 8 &&
!password.toLowerCase().includes('password')
}

export { isValidPassword }

isValidPassword
false

104
import { isValidPassword } from '../src/utils/user.js'

test('Should reject password shorter than 8 characters', () => {


const isValid = isValidPassword('abc123')

expect(isValid).toBe(false)
})

npm install parcel-bundler@1.9.7

package.json

{
"scripts": {
"start": "parcel src/index.html"
},
"devDependencies": {
"parcel-bundler": "^1.9.7"
}
}

npm run start npm start

105
npm install apollo-boost@0.1.16

import ApolloBoost, { gql } from 'apollo-boost'

const client = new ApolloBoost({


uri: 'http://localhost:4000'
})

106
const getUsers = gql`
query {
users {
id
name
}
}
`

client.query({
query: getUsers
}).then((response) => {
let html = ''

response.data.users.forEach((user) => {
html += `
<div>
<h3>${user.name}</h3>
</div>
`
})

document.getElementById('users').innerHTML = html
})

env-cmd

107
npm install env-cmd@8.0.2

package.json
globalSetup
globalTeardown

{
"scripts": {
"test": "env-cmd ./config/test.env jest --watch"
},
"jest": {
"globalSetup": "./tests/jest/globalSetup.js",
"globalTeardown": "./tests/jest/globalTeardown.js"
}
}

require('babel-register')
require('@babel/polyfill')
const server = require('../../src/server').default

module.exports = async () => {


// Do something to start the app up
global.httpServer = await server.start({ port: 4000 })
}

module.exports = async () => {


// Do something to tear the app down
await global.httpServer.close()
}

108
test('Should create a new user', async () => {
const createUser = gql`
mutation {
createUser(
data: {
name: "Andrew",
email: "andrew@example.com",
password: "MyPass123"
}
){
token,
user {
id
}
}
}
`

const response = await client.mutate({


mutation: createUser
})

const exists = await prisma.exists.User({ id:


response.data.createUser.user.id })
expect(exists).toBe(true)
})

109
beforeAll beforeEach afterAll afterEach
beforeEach

beforeEach(async () => {
// Wipe all test data and data that test cases may have added
await prisma.mutation.deleteManyUsers()

// Add test data base in


const user = await prisma.mutation.createUser({
data: {
name: 'Jen',
email: 'jen@example.com',
password: bcrypt.hashSync('Red098!@#$')
}
})
})

110
test('Should expose published posts', async () => {
const getPosts = gql`
query {
posts {
id
title
body
published
}
}
`
const response = await client.query({ query: getPosts })

expect(response.data.posts.length).toBe(1)
expect(response.data.posts[0].published).toBe(true)
})

toThrow

toThrow expect

test('Should throw an error', async () => {


expect(() => {
throw new Error()
}).toThrow()
})

toThrow
expect toThrow

111
test('Should not login with bad credentials', async () => {
const login = gql`
mutation {
login(
data: {
email: "jen@example.com",
password: "red098!@#$"
}
){
token
}
}
`

await expect(
client.mutate({ mutation: login })
).rejects.toThrow()
})

request

getClient

112
import ApolloBoost from 'apollo-boost'

const getClient = (jwt) => {


return new ApolloBoost({
uri: 'http://localhost:4000',
request(operation) {
if (jwt) {
operation.setContext({
headers: {
Authorization: `Bearer ${jwt}`
}
})
}
}
})
}

export { getClient as default }

test('My test case', async () => {


const client = getClient(userOne.jwt)
// Send off an operation that requires authentication
})

createUser

113
$
$data $data data
createUser

const createUser = gql`


mutation($data:CreateUserInput!) {
createUser(
data: $data
){
token,
user {
id
name
email
}
}
}
`

const variables = {
data: {
name: 'Andrew',
email: 'andrew@example.com',
password: 'MyPass123'
}
}

const response = await client.mutate({


mutation: createUser,
variables
})

114
getClient.js

done
done

test('Should subscribe to comments for a post', async (done) => {


const variables = {
postId: postOne.post.id
}
client.subscribe({ query: subscribeToComments, variables }).subscribe({
next(response) {
expect(response.data.comment.mutation).toBe('DELETED')
done()
}
})

await prisma.mutation.deleteComment({ where: { id: commentOne.comment.id


}})
})

115
PRISMA_ENDPOINT

116
prisma prisma
deploy -e
npm install
npm run test
npm run dev
heroku create
heroku config:set KEY=VALUE
prod.env
git push heorku master

117

You might also like