2.1 PDF-Reference-The-Modern-GraphQL-Bootcamp PDF
2.1 PDF-Reference-The-Modern-GraphQL-Bootcamp PDF
2.1 PDF-Reference-The-Modern-GraphQL-Bootcamp PDF
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": "[email protected]"
}
}
}
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
.babelrc
babel-preset-env
{
"presets": [
"env"
]
}
{
"scripts": {
"start": "babel-node src/index.js"
}
}
11
export
// utilities.js
const add = (a, b) => a + b
const subtract = (a, b) => a - b
import
// index.js
import { add, subtract } from './utilities'
add subtract
square
12
// utilities.js
const add = (a, b) => a + b
const subtract = (a, b) => a - b
const square = (x) => x * x
square otherSquare
add
// index.js
import otherSquare, { add } from './utilities'
graphql-yoga
13
npm install [email protected]
graphql-yoga
// Resolvers
const resolvers = {
Query: {
hello() {
return 'This is my first query!'
}
}
}
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 null
price price
17
npm install [email protected] --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
}
]
}
}
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!
}
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: "[email protected]"){
id
name
email
age
}
}
{
"data": {
"createUser": {
"id": "8257f14f-80b0-4313-ad35-9047e3b5f851",
"name": "Andrew",
"email": "[email protected]",
"age": null
}
}
}
createUser
32
{
"data": null,
"errors": [
{
"message": "Email taken",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createUser"
]
}
]
}
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 [email protected]
.babelrc plugins
{
"presets": [
"env"
],
"plugins": [
"transform-object-rest-spread"
]
}
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
data
CreateUserInput
mutation {
createUser(
data:{
name:"Jess",
email:"[email protected]"
}
){
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 (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
users
db db.users
const resolvers = {
// Other properties hidden for brevity
Query: {
users(parent, args, { db }, info) {
if (!args.query) {
return db.users
}
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 (emailTaken) {
throw new Error('Email taken')
}
user.email = data.email
}
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'
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')
}
}
}
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')
}
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 (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 [email protected]
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
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"
}
}
// prisma.js
import { Prisma } from 'prisma-binding'
62
datamodel.graphql
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')
}
return post.author
}
updatePostForUser catch
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
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
}]
}
}
"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
})
}
}
71
const User = {
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')
}
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'
prisma token
{
"Authorization":"Bearer YourTokenHere"
}
75
{
"projects": {
"prisma": {
"schemaPath": "src/generated/prisma.graphql",
"extensions": {
"prisma": "prisma/prisma.yml",
"endpoints": {
"default": "http://localhost:4466"
}
}
}
}
}
get-schema package.json
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.')
}
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.')
}
return {
user,
token: jwt.sign({ userId: user.id }, 'thisisasecret')
}
}
}
78
{
"Authorization": "Bearer
eyJhbGciaiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjamtqczMBcGYwMDQ3MDg3MzJt
NHJoaWFjIiwiaWF0IjoxNTMzNjU2NzE2fQ.MHSey8h1xeIfUmMvV75Vhmzvbx7R7jR65ZWr--
r2oVY"
}
context
request
createPost
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
if (!header) {
throw new Error('Authentication required')
}
return decoded.userId
}
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
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)
if (posts.length === 0) {
throw new Error('Post not found')
}
return posts[0]
}
}
schema.graphql
String! String
User.email
83
getUserId requireAuth false
null
const User = {
email(parent, args, { request }, info) {
const userId = getUserId(request, false)
userFields
User User
userFields
users
84
query {
users {
...userFields
posts{
id
title
published
}
}
}
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
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)
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 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
}]
}
}
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
}]
}
}
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
}]
}
}
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
97
prisma login
prod.env dev.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
prisma.js
http://localhost:4466 PRISMA_ENDPOINT
PRISMA_ENDPOINT
env-cmd
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
src/index.js
100
import '@babel/polyfill/noConflict'
node_modules
config
.gitignore
node_modules/
config/
git init
git add
git add .
101
heroku create
config
test.env default
test
102
PRISMA_ENDPOINT=http://localhost:4466/default/test
PRISMA_SECRET=pi389xjam2b3pjsd0
JWT_SECRET=23oijdsm23809sdf
prisma
jest
{
"scripts": {
"test": "jest --watch"
}
}
.test.js tests
103
test('Should create a new user', () => {
})
test
isValidPassword
export { isValidPassword }
isValidPassword
false
104
import { isValidPassword } from '../src/utils/user.js'
expect(isValid).toBe(false)
})
package.json
{
"scripts": {
"start": "parcel src/index.html"
},
"devDependencies": {
"parcel-bundler": "^1.9.7"
}
}
105
npm install [email protected]
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 [email protected]
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
108
test('Should create a new user', async () => {
const createUser = gql`
mutation {
createUser(
data: {
name: "Andrew",
email: "[email protected]",
password: "MyPass123"
}
){
token,
user {
id
}
}
}
`
109
beforeAll beforeEach afterAll afterEach
beforeEach
beforeEach(async () => {
// Wipe all test data and data that test cases may have added
await prisma.mutation.deleteManyUsers()
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
toThrow
expect toThrow
111
test('Should not login with bad credentials', async () => {
const login = gql`
mutation {
login(
data: {
email: "[email protected]",
password: "red098!@#$"
}
){
token
}
}
`
await expect(
client.mutate({ mutation: login })
).rejects.toThrow()
})
request
getClient
112
import ApolloBoost from 'apollo-boost'
createUser
113
$
$data $data data
createUser
const variables = {
data: {
name: 'Andrew',
email: '[email protected]',
password: 'MyPass123'
}
}
114
getClient.js
done
done
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