KEMBAR78
Fullstack Graphql | PDF | Domain Name System | Computing
0% found this document useful (0 votes)
47 views126 pages

Fullstack Graphql

The document is a comprehensive guide on Fullstack GraphQL by Julian Mayorga, covering various aspects of GraphQL including its definition, data modeling, APIs, clients, and subscriptions. It is organized into multiple sections with detailed subtopics, providing a structured approach to learning GraphQL. The content is designed to assist developers in building applications using GraphQL effectively.

Uploaded by

Ujjwal Dhakal
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
0% found this document useful (0 votes)
47 views126 pages

Fullstack Graphql

The document is a comprehensive guide on Fullstack GraphQL by Julian Mayorga, covering various aspects of GraphQL including its definition, data modeling, APIs, clients, and subscriptions. It is organized into multiple sections with detailed subtopics, providing a structured approach to learning GraphQL. The content is designed to assist developers in building applications using GraphQL effectively.

Uploaded by

Ujjwal Dhakal
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/ 126

Fullstack GraphQL

Julian Mayorga
© 2018 Julian Mayorga
Contents

PREFACE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
So, what is GraphQL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Organization of the book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Sample application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Development environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1. Reading and writing data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4


1.1 Queries and Mutations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Nested Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4 Multiple fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5 Operation name . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.6 Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.7 Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.8 Fragments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.9 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.10 Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.11 Default variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.12 Inline fragments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.13 Meta fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.14 Mutations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.15 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

2. Data modeling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.1 Schema, types and resolvers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2 Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.3 Type definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.4 Resolvers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

3. GraphQL APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.1 Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.3 Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
CONTENTS

3.4 File organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48


3.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

4. GraphQL clients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.1 Initial React client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.2 Client side state . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.3 Apollo Client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.4 React Apollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.5 Query component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
4.6 Apollo Provider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.7 Mutation component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

5. Subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1 Server side subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.2 PubSub systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.3 Implementing server side Subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.4 Client side subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.5 Apollo boost migration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.6 Implementing client side subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

6. Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
6.1 How to test GraphQL APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
6.2 Testing setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.3 GraphQL layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
6.4 HTTP Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.5 Testing email based authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.6 Subscription endpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
6.7 How to test React Apollo GraphQL clients . . . . . . . . . . . . . . . . . . . . . . . . 107
6.8 Testing client-side authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.9 Client subscriptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
PREFACE
GraphQL is revolutionizing client-server communication. It is a technology that enables better
documented APIs, easier data management in HTTP clients, and optimized network usage.
One of the main benefits of GraphQL is that improves communication between APIs and API
consumers. Facilitates team communication by providing an easy way for frontend developers to
know all methods that the API exposes. It also enables better communication with 3rd party API
consumers because GraphQL services have zero configuration API documentation.
It empowers clients by giving them complete data fetching control. GraphQL lets clients ask for the
exact data that they need. Not more, not less. It also lets clients ask for nested resources in the same
operation, avoiding the need for REST-style cascading requests. REST tends to push complexity to
API clients.
Another benefit of GraphQL is that it optimizes network usage by reducing HTTP payloads and
number of requests. Reducing data and requests directly maps to a better experience for mobile
users.

So, what is GraphQL?


GraphQL is a domain specific typed language to design and query data.
A domain specific language, or DSL, is a language built for a single application domain. They are
the opposite of general purpose languages like Javascript, Ruby, Python or C, which are applicable
across different domains. There are many popular DSLs in use nowadays, CSS is a DSL for styling
and HTML is a DSL for markup. GraphQL is a DSL for data.
It is a typed language. This means that it uses types to define resources, it adds types to each
resource’s fields. It also uses types to statically check for errors. Being a typed language is the source
of many of GraphQL’s biggest assets, like enabling automatic API introspection and documentation.
GraphQL’s domain is data. It can be used to design a schema that represents data and also to ask for
specific fields on data.
Developing API servers and clients is the main use case of GraphQL. Backend developers can use
GraphQL to model their data, while frontend developers can use GraphQL to write queries to retrieve
specific bits of data.
Even though services generally expose GraphQL through their HTTP layer, GraphQL is not tied to
HTTP or any other communication protocol.
GraphQL is a specification. This means that it specifies how it should work, allowing anyone to
implement GraphQL in any programming language. There is an official implementation in Javascript
PREFACE 2

called graphql-js, but there are also many other incarnations in other programming languages like
Ruby, Elixir and more.

Organization of the book


With this book you will learn how to develop a complete GraphQL client-server application from
scratch. You will learn how to fetch data from the client, how to design that data in the server, how
to develop NodeJS GraphQL servers and finally how to create React GraphQL clients.
The first two chapters will teach you how to fetch data using GraphQL. The first chapter will teach
you how to create data. The second chapter will teach you how to design schemas. You will learn
these pure GraphQL concepts, without the need of thinking about HTTP servers or clients. GraphQL
is an abstraction that allows you to think about data without worrying about transport. You will
build a schema, queries and mutations using Javascript and a couple of GraphQL libraries.
The rest of the chapters will focus on building GraphQL servers and clients.
The third chapter, GraphQL APIs, will teach you how build GraphQL HTTP servers using NodeJS
and Apollo server. You will learn how to expose a GraphQL schema over HTTP, how to connect to
a database, how to handle authentication and authorization and how to organize your files.
In the fourth chapter, GraphQL Clients, you will learn how to write GraphQL clients using React
and Apollo client. You will learn how to ask for and create data using Apollo’s Query and Mutation
components, and also how to handle authentication.
The fifth chapter will teach you how to add real time functionality to your GraphQL applications
using Subscriptions. Subscriptions provide GraphQL APIs the ability to push data to the clients.
You will learn how to test GraphQL APIs and clients in the sixth chapter.

Sample application
Through the course of this book you will learn how to build a Pinterest clone called Pinapp using
GraphQL, NodeJS, React and Apollo client.
Pinapp should allow users to:

• Login with magic links


• Logout
• Add pins (a pin is an image that links to a URL)
• Search pins and users
• List pins
• See new pins without refreshing browser
PREFACE 3

You will build this application in layers. First you will design the data layer, then write the business
logic, after that create HTTP transport layer, then connect everything to the database layer and
finally build the HTTP client.

Development environment
There are no environment requirements to try the examples in this book, other than having a web
browser and internet connection. Every step of the application has a live, editable online version
hosted at glitch.com. Glitch is an awesome community to build apps created by the folks that
developed Stack Overflow and Trello.
1. Reading and writing data
In this chapter you will learn how to use GraphQL from a frontend developer’s perspective. This
chapter explains how to use queries and mutations to read and write data from GraphQL.

Queries and mutations

As you continue to learn the ins and outs of GraphQL, you will realize that it is a technology that
makes life much easier for frontend developers. It gives them complete control of the data that they
want from the server.
Making life easier for clients has been one of the main goals for the team that created GraphQL. The
evolution of the language has been the result of Client-Driven development¹.

1.1 Queries and Mutations


In its simplest form, GraphQL is all about asking for specific fields of objects.
The GraphQL query language defines how to interact with data using GraphQL’s queries and
mutations. Queries let you ask for data, whereas Mutations let you write data. Queries serve the
¹https://youtu.be/vQkGO5q52uE
1. Reading and writing data 5

same purpose as REST’s GET requests, and you could think of mutations as analogous to REST’s
POST, PUT, PATCH and DELETE requests.
The rest of this chapter will teach you the following features of GraphQL syntax:

• Basic query
• Query nested fields
• Query multiple fields
• Operation name
• Arguments
• Aliases
• Fragments
• Variables
• Directives
• Default variables
• Mutations
• Inline fragments
• Meta fields

All concepts that you will learn have a runnable example, implemented using graphql-js². GraphQL
JS is the reference implementation of GraphQL, built with Javascript. This library exports a function
called graphql which lets us send a query to a GraphQL schema.
The examples in this chapter contain a sample GraphQL schema. Don’t worry if you don’t
understand it yet. We will focus on the querying part in this chapter, while the next one will focus
on how to create the schema. Please note that this schema returns mock data, so don’t expect much
more than random numbers or a bunch of "Hello world". The next chapter will teach you how to
design this schema properly.
Even though GraphQL is meant to be exposed by an HTTP server and consumed by an HTTP client,
running GraphQL queries using Javascript will help you understand the basics of the language,
without any overhead.
You will use a function called graphql, which graphql-js exports. The main use case of this function
receives two arguments and returns a promise. The first argument is an object that represents a
GraphQL schema. The second argument is a string containing a GraphQL query. All examples in
this chapter will teach you how to write this query string. Please refer to the API documentation of
graphql-js³ to know more about it.

²https://github.com/graphql/graphql-js
³http://graphql.org/graphql-js/graphql/#graphql
1. Reading and writing data 6

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = ``;
6
7 graphql(schema, query).then(result =>
8 console.log(JSON.stringify(result, null, 1))
9 );

Remix this example on glitch to run all the queries in this chapter. Remixing means creating your
own copy of a project. This will give you complete freedom over the project. You can modify it at
will, run scripts using a console, and even export it to github.
Remix queries and mutations example⁴
Once you have remixed this project, you can run any of the scripts in the queries folder. Try it out
by opening the URL of your remixed project. After you open it, open its console by clicking “Logs”
and then “Console”. Run node queries/1-query.js to see the output of the first script.
You have everything you need to start learning GraphQL query syntax. Let’s start by sending basic
queries.

1.2 Query
As we said at the start of this chapter, GraphQL is all about asking for specific fields of objects. A
query defines which fields the GraphQL JSON response will have. The syntax for achieving this
looks similar to writing a JSON object with just the keys, excluding the values. For example, if you
wanted to get a list of users, each one with an email field, you could write the following query:

1 {
2 users {
3 email
4 }
5 }

You can send the previous query along with the example schema to the graphql function. Remember,
the second argument that graphql receives is a query string. Let’s see an example script.
queries/1-query.js

⁴https://glitch.com/edit/#!/remix/pinapp-queries-mutations
1. Reading and writing data 7

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 {
7 users {
8 email
9 }
10 }
11 `;
12
13 graphql(schema, query).then(result =>
14 console.log(JSON.stringify(result, null, 1))
15 );

Running the previous script in the console returns a response that has all the fields that you asked
for in the query, plus it has a top level "data" key. Inside that key, you will see a structure that
matches exactly the query that we sent. It has a "users" key, which contains an array of objects
with an "email" key.

1 $ node queries/1-query.js
2 {
3 "data": {
4 "users": [
5 {
6 "email": "Hello World"
7 },
8 {
9 "email": "Hello World"
10 }
11 ]
12 }
13 }

1.3 Nested Fields


You can query nested fields using GraphQL. One of the great advantages of GraphQL over REST is
fetching nested resources in a single query. You can ask for a resource, for example users, and a list
of nested resources, for example pins, in a single query. In order to do that with REST, you would
have to get users and pins in separate HTTP requests.
1. Reading and writing data 8

Note that only fields with Object type can have nested fields. You can’t ask for nested fields in other
types, like String, Int or others.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 {
7 users {
8 email
9 pins {
10 title
11 }
12 }
13 }
14 `;
15
16 graphql(schema, query).then(result =>
17 console.log(JSON.stringify(result, null, 1))
18 );

The above example shows how simple it is asking for nested resources. As you can imagine, running
the previous example returns a JSON object with the exact keys that the query specifies. Try it out
by running node queries/2-fields.js in your project’s console.

1 $ node queries/2-fields.js
2 {
3 "data": {
4 "users": [
5 {
6 "email": "Hello World",
7 "pins": [
8 {
9 "title": "Hello World"
10 },
11 {
12 "title": "Hello World"
13 }
14 ]
15 },
16 {
1. Reading and writing data 9

17 "email": "Hello World",


18 "pins": [
19 {
20 "title": "Hello World"
21 },
22 {
23 "title": "Hello World"
24 }
25 ]
26 }
27 ]
28 }
29 }

1.4 Multiple fields


GraphQL allows you to query for multiple fields in a single query. You saw in the previous example
that you can query nested resources, well you can also query for totally unrelated resources in the
same operation.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 {
7 users {
8 email
9 }
10 pins {
11 title
12 }
13 }
14 `;
15
16 graphql(schema, query).then(result =>
17 console.log(JSON.stringify(result, null, 1))
18 );

Now you are starting to see that GraphQL queries are really about asking for specific fields of objects.
If you run node queries/3-multiple-fields.js, you will get an object with two keys, users and
pins.
1. Reading and writing data 10

1 $ node queries/3-multiple-fields.js
2 {
3 "data": {
4 "users": [
5 {
6 "email": "Hello World"
7 },
8 {
9 "email": "Hello World"
10 }
11 ],
12 "pins": [
13 {
14 "title": "Hello World"
15 },
16 {
17 "title": "Hello World"
18 }
19 ]
20 }
21 }

1.5 Operation name


Up until this point, you were using the short hand syntax of GraphQL queries, but there is also
a longer syntax that gives you more options. The longer syntax includes the query keyword, and
the operation name. Many times you will need to use this syntax because it allows you to specify
variables, or use different operations like mutations or subscriptions, which we will cover in the rest
of the book.
This is how a query with the operation name GetUsers looks like:

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query GetUsers {
7 users {
8 email
9 pins {
10 title
1. Reading and writing data 11

11 }
12 }
13 }
14 `;
15
16 graphql(schema, query).then(result =>
17 console.log(JSON.stringify(result, null, 1))
18 );

You can run the previous query by entering node queries/4-operation-name.js in the console.
Notice that it behaves exactly like the short hand version of the query.

1 $ node queries/4-operation-name.js
2 {
3 "data": {
4 "users": [
5 {
6 "email": "Hello World",
7 "pins": [
8 {
9 "title": "Hello World"
10 },
11 {
12 "title": "Hello World"
13 }
14 ]
15 },
16 {
17 "email": "Hello World",
18 "pins": [
19 {
20 "title": "Hello World"
21 },
22 {
23 "title": "Hello World"
24 }
25 ]
26 }
27 ]
28 }
29 }
1. Reading and writing data 12

1.6 Arguments
All fields can have arguments, which you can use the same way you would use function arguments.
You could think of GraphQL fields as functions, more so than properties. Picturing them as functions
provides a clearer picture regarding what you can do by passing arguments to them.
Let’s say for example that you want to query a pin by id by querying a field called pinById. You
could ask for the pin with id 1 by passing a named argument to the query, like this:

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query {
7 pinById(id: "1") {
8 title
9 }
10 }
11 `;
12
13 graphql(schema, query).then(result =>
14 console.log(JSON.stringify(result, null, 1))
15 );

Running node queries/5-arguments.js in the console yields the following output.

1 $ node queries/5-arguments.js
2 {
3 "data": {
4 "pinById": {
5 "title": "Hello World"
6 }
7 }
8 }

1.7 Aliases
What happens if you want to query the same field twice in a single query? Well you can achieve
that using aliases. Aliases let you associate a name to a field, so that the response will have the alias
you specified instead of the key name.
1. Reading and writing data 13

Aliasing a field is as simple as prepending the field name with the desired alias and a colon (:).
Aliases are especially helpful when querying for the same field but with different arguments. The
following query asks for pinById twice, aliasing the first field with firstPin and the second field
with secondPin.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query {
7 firstPin: pinById(id: "1") {
8 title
9 }
10 secondPin: pinById(id: "2") {
11 title
12 }
13 }
14 `;
15
16 graphql(schema, query).then(result =>
17 console.log(JSON.stringify(result, null, 1))
18 );

The response contains the aliases instead of the field name. Verify this by running node queries/6-
aliases.js.

1 $ node queries/6-aliases.js
2 {
3 "data": {
4 "firstPin": {
5 "title": "Hello World"
6 },
7 "secondPin": {
8 "title": "Hello World"
9 }
10 }
11 }
1. Reading and writing data 14

1.8 Fragments
GraphQL syntax provides a way to reuse a set of fields with the fragment keyword. This is a language
designed for querying fields, so it seems natural to have a way to reuse fields in different parts of
the query.
In order to reuse fields you have to first define a fragment and then place the fragment in different
parts of the query.
Define fragments using the fragment [fragmentName] on [Type] { field anotherField } syn-
tax. Use fragments by placing ...[fragmentName] anywhere you would place a field.
An example is worth more than 1000 keywords. The following example defines a fragment called
pinFields, and uses it twice in the query.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query {
7 pins {
8 ...pinFields
9 }
10 users {
11 email
12 pins {
13 ...pinFields
14 }
15 }
16 }
17 fragment pinFields on Pin {
18 title
19 }
20 `;
21
22 graphql(schema, query).then(result =>
23 console.log(JSON.stringify(result, null, 1))
24 );

Run the previous query with node queries/7-fragments.js. Play with the defined fragment by
changing the list of fields that you ask for, and see how that changes the output of the script.
1. Reading and writing data 15

1 $ node queries/7-fragments.js
2 {
3 "data": {
4 "pins": [
5 {
6 "title": "Hello World"
7 },
8 {
9 "title": "Hello World"
10 }
11 ],
12 "users": [
13 {
14 "email": "Hello World",
15 "pins": [
16 {
17 "title": "Hello World"
18 },
19 {
20 "title": "Hello World"
21 }
22 ]
23 },
24 {
25 "email": "Hello World",
26 "pins": [
27 {
28 "title": "Hello World"
29 },
30 {
31 "title": "Hello World"
32 }
33 ]
34 }
35 ]
36 }
37 }

1.9 Variables
Just like fragments lets you reuse field sets, variables let you reuse queries. Using variables you can
specify which parts of the query are configurable, so that you can use the query multiple times by
1. Reading and writing data 16

changing the variable values. Using variables you can construct dynamic queries.
You can add a list of variables names, along with their types, in the same place that you specify the
query keyword.
Let’s see how you could add variables to the example of querying pins by id. You could define a
variable called $id, specify its type as String and mark it as required by putting an exclamation
mark (!) after its type.
The next snippet defines the $id variable in its query, and sends it along with the schema and a list
of variables to graphql. This graphql function receives a list of variables as its fifth argument.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query ($id: String!) {
7 pinById(id: $id) {
8 title
9 }
10 }
11 `;
12
13 graphql(schema, query, undefined, undefined, {
14 id: "1"
15 }).then(result =>
16 console.log(JSON.stringify(result, null, 1))
17 );

The result of running node queries/8-variables.js is pretty straightforward.

1 $ node queries/8-variables.js
2 {
3 "data": {
4 "pinById": {
5 "title": "Hello World"
6 }
7 }
8 }

1.10 Directives
Just as variables let you create dynamic queries by changing arguments, directives allow you to
construct dynamic queries that modify the structure and shape of their result.
1. Reading and writing data 17

You can attach directives to fields or fragments. All directives start with an @ symbol.
GraphQL servers can expose any number of directives that they wish, but the GraphQL spec defined
two mandatory directives, @include(if: Boolean) and @skip(if: Boolean). The first includes a
field only when if is true, and the second skips a field when if is true.
The next example shows directives in action. It places an @include directive on the pins field, and
parameterizes the value using a variable called $withPins.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query ($withPins: Boolean!) {
7 users {
8 email
9 pins @include(if: $withPins) {
10 title
11 }
12 }
13 }
14 `;
15
16 graphql(schema, query, undefined, undefined, {
17 withPins: true
18 }).then(result =>
19 console.log(JSON.stringify(result, null, 1))
20 );

Go ahead and run the previous example with node queries/9-directives.js. Change withPins
to false and see how the result’s structure changes.

1 $ node queries/9-directives.js
2 {
3 "data": {
4 "users": [
5 {
6 "email": "Hello World",
7 "pins": [
8 {
9 "title": "Hello World"
10 },
1. Reading and writing data 18

11 {
12 "title": "Hello World"
13 }
14 ]
15 },
16 {
17 "email": "Hello World",
18 "pins": [
19 {
20 "title": "Hello World"
21 },
22 {
23 "title": "Hello World"
24 }
25 ]
26 }
27 ]
28 }
29 }

1.11 Default variables


GraphQL syntax lets you define default values to variables. You can achieve this by adding an equals
sign (=) after the variable’s type.
Let’s see an example by adding a default parameter of true to the previous Directives example. A
default variable allows you to call graphql in the example without sending withPins in the list of
variables.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query ($withPins: Boolean = true) {
7 users {
8 email
9 pins @include(if: $withPins) {
10 title
11 }
12 }
13 }
1. Reading and writing data 19

14 `;
15
16 graphql(schema, query).then(result =>
17 console.log(JSON.stringify(result, null, 1))
18 );

Run node queries/10-default-variables.js. Notice that the output looks exactly the same as
calling graphql with a withPins value of true.

1 $ node queries/10-default-variables.js
2 {
3 "data": {
4 "users": [
5 {
6 "email": "Hello World",
7 "pins": [
8 {
9 "title": "Hello World"
10 },
11 {
12 "title": "Hello World"
13 }
14 ]
15 },
16 {
17 "email": "Hello World",
18 "pins": [
19 {
20 "title": "Hello World"
21 },
22 {
23 "title": "Hello World"
24 }
25 ]
26 }
27 ]
28 }
29 }
1. Reading and writing data 20

1.12 Inline fragments


Inline fragments provide a way to specify a list of fields inline. As opposed to regular fragments,
which must be defined using the fragment keyword, inline fragments don’t need to be defined
anywhere.
These types of fragments are useful when querying fields with a Union or Interface type. These
fields can return objects with varying fields, depending on the object’s type. You can use fragments
to indicate which fields to return, based on an object’s type.
A great use case for inline fragments is a search query, which can return objects of different types.
The following snippet shows how you could use inline fragments to get a different set of fields from
a search query. If the returned object is a Person, return its email, and if this object is a Pin, return
its title.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query ($text: String!) {
7 search(text: $text) {
8 ... on Person {
9 email
10 }
11 ... on Pin {
12 title
13 }
14 }
15 }
16 `;
17
18 graphql(schema, query, undefined, undefined, {
19 text: "Hello world"
20 }).then(result =>
21 console.log(JSON.stringify(result, null, 1))
22 );

Run the previous example with node queries/11-inline-fragments.js.


1. Reading and writing data 21

1 $ node queries/11-inline-fragments.js
2 {
3 "data": {
4 "search": [
5 {
6 "title": "Hello World"
7 },
8 {
9 "email": "Hello World"
10 }
11 ]
12 }
13 }

1.13 Meta fields


Queries can request meta fields, which are special fields that contain information about a schema.
GraphQL allows you to retrieve the type name of objects by requesting a meta field called __-
typename.

This meta field is useful in the same scenarios where inline fragments are handy, which is in queries
that can return multiple field types, like Union or Interface.
The following snippet adds a __typename field to the example search query from the inline
fragments explanation.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 query ($text: String!) {
7 search(text: $text) {
8 __typename
9 ... on Person {
10 email
11 }
12 ... on Pin {
13 title
14 }
15 }
16 }
1. Reading and writing data 22

17 `;
18
19 graphql(schema, query, undefined, undefined, {
20 text: "Hello world"
21 }).then(result =>
22 console.log(JSON.stringify(result, null, 1))
23 );

Run the previous script by entering node queries/12-meta-fields.js into the console. You will
see that the response contains a __typename field in each object.

1 $ node queries/12-meta-fields.js
2 {
3 "data": {
4 "search": [
5 {
6 "__typename": "Admin",
7 "email": "Hello World"
8 },
9 {
10 "__typename": "Pin",
11 "title": "Hello World"
12 }
13 ]
14 }
15 }

1.14 Mutations
GraphQL syntax provides a way to create data with the mutation keyword. It works similarly to
the query keyword. It supports variables, you can ask for specific fields in the response, and all the
other features that we have talked about. As opposed to queries, mutations don’t have shorthand
forms, this means that they always start with the mutation keyword.
Even though mutations signify data changes, this is merely a convention. There is nothing that
enforces that servers actually change data inside mutations. Similarly, there is nothing enforcing
that queries don’t contain any data changes. This convention is similar to the REST conventions
that recommend GET requests to not have any side effects, or POST requests to create resources. It
is not enforced in any way, but you should follow in order to not send any unexpected surprises to
your API consumers.
Let’s see how mutations work in practice by sending a mutation called addPin, exposed by the
example schema we were using in this chapter.
1. Reading and writing data 23

You will notice that writing mutations is really similar to writing queries. The only differences are
the initial keyword and the fact that it signifies a data change.

1 const { graphql } = require("graphql");


2
3 const schema = require("../schema");
4
5 const query = `
6 mutation AddPin($pin: PinInput!) {
7 addPin(pin: $pin) {
8 id
9 title
10 link
11 image
12 }
13 }
14 `;
15
16 graphql(schema, query, undefined, undefined, {
17 pin: {
18 title: "Hello world",
19 link: "Hello world",
20 image: "Hello world"
21 }
22 }).then(result =>
23 console.log(JSON.stringify(result, null, 1))
24 );

Run this mutation example by entering node queries/13-mutations.js in the console. Remember
that our schema works with mocked data, it does not have a real implementation underneath, so
don’t expect any data changes caused by this mutation.

1 $ node queries/13-mutations.js
2 {
3 "data": {
4 "addPin": {
5 "id": "Hello World",
6 "title": "Hello World",
7 "link": "Hello World",
8 "image": "Hello World"
9 }
10 }
11 }
1. Reading and writing data 24

If you query the list of pins after your last mutation, you will notice that this last mutation did not
generate any data. This happens because the queries in this chapter go against a mocked schema.

1.15 Summary
GraphQL makes frontend development easier by providing powerful querying capabilities. It makes
it easy to fetch for multiple, nested resources in a single query. Fetching the minimal set of fields
needed from a resource is also a built-in feature.
In the next chapter, called Data Modeling, you will design from scratch the schema you used in this
chapter. As opposed to this chapter’s schema, the next one will be backed by an in-memory database,
it will not have mocked values.
2. Data modeling
In the previous chapter you learned how to read and write data by sending queries against a schema
using the GraphQL query language. In this chapter you will learn how to model the data behind the
queries using schemas and types. To create this schema you will use the GraphQL Schema Definition
Language (also called SDL, not to be confused with LSD).
Whereas the previous chapter focused on how clients interact with servers using GraphQL, this
chapter will tackle how to expose a data model that clients can consume.
Remember the Pinterest clone we talked about in the introduction? After learning the concepts
behind GraphQL schemas and types, you will design its data model at the end of this chapter.

2.1 Schema, types and resolvers


GraphQL servers expose their schema in order to let clients know which queries and mutations are
available. To define what a schema looks like, you need to define the types of all fields. To define
how a schema behaves, you need to define a function that the server will run when a client asks for
a field, this function is called resolver. A schema needs both type definitions and resolvers.
2. Data modeling 26

Types and resolvers

Because GraphQL is a specification implemented in many languages, it provides its own language to
design schemas, called SDL. You write type definitions in SDL, but you can create resolvers in any
language that implements the GraphQL specification. This book focuses on the Javascript GraphQL
ecosystem, so you will write all resolvers in this language.
The schema you will create is more than just an example that illustrates how to write SDL. It is the
initial step of building PinApp, the sample application of this book. It allows most of the features in
the final app:

• Login with magic links


• Allow authenticated users to add pins
• Search pins and users
• List pins

Make your own copy of this example with the following button:
2. Data modeling 27

Remix schema example⁵

After remixing, closely follow the instructions in README.md. This project’s README
instructs you to configure environment variables in .env.

Note that this schema is not exposed over HTTP. It is accessible with scripts using graphql-js. The
next chapter will show you how to add an HTTP layer to this schema, using Apollo Server.
In the next section you will understand how to create schemas using a function called makeExecuta-
bleSchema.

2.2 Schemas
You create schemas by combining type definitions and resolvers. There is a handy package called
graphql-tools⁶ that provides a function called makeExecutableSchema. The previous chapter
contained a lot of graphql(query, schema) calls. All of those examples sent queries agains a schema
generated with makeExecutableSchema.
Open the file called schema.js in the example project you just remixed to see how you can create a
schema.

1 const { makeExecutableSchema } = require("graphql-tools");


2 const { importSchema } = require("graphql-import");
3
4 const typeDefs = importSchema("schema.graphql");
5 const resolvers = require("./resolvers");
6
7 const schema = makeExecutableSchema({
8 typeDefs,
9 resolvers
10 });
11
12 module.exports = schema;

As you can see, this file created a schema with types from schema.graphql and resolvers from
resolvers.js. The next two sections will teach you how to create these type definitions and
resolvers.
⁵https://glitch.com/edit/#!/remix/pinapp-schema
⁶https://github.com/apollographql/graphql-tools
2. Data modeling 28

2.3 Type definitions


In this section you will learn how to write GraphQL types using SDL. A type is just a representation
of an object in your schema. Objects, as in many other programming languages, can have many
fields.

You can find all examples in this section in schema.graphql

This is how you define an object type:

1 type Pin {
2 title: String!
3 link: String!
4 image: String!
5 id: String!
6 user_id: String!
7 }

As you can see, you can define the type of fields after the field name. In the case of Pin, all of its
fields are of type String, and are required because they end with an exclamation mark (!).
GraphQL defines two special object types, Query and Mutation. They are special because they define
the entry points of a schema. Being the entry point of a schema means that GraphQL clients must
start their queries with one or more of the fields from Query.

1 type Query {
2 pins: [Pin]
3 pinById(id: String!): Pin
4 users: [User]
5 me: User
6 search(text: String): [SearchResult]
7 }

As you may have noticed, object types can have arguments. Every field has an underlying function
(called resolver) that runs before returning its value, so it makes sense to think of field arguments
the same way we think of function arguments.
Another new element in the previous Query is the List type modifier. You can wrap fields in square
brackets to specify them as lists.
The GraphQL specification determines that all schemas must have a Query type, and they can
optionally have a Mutation type. This is how PinApp’s Mutation type looks like:
2. Data modeling 29

1 type Mutation {
2 addPin(pin: PinInput!): Pin
3 sendShortLivedToken(email: String!): Boolean
4 createLongLivedToken(token: String!): String
5 }

Notice that addPin has a pin argument of type PinInput, and the other two fields have arguments
of String type. You can’t pass arguments of type Object as arguments, you can only pass scalar
types or Input types.
Scalar types can’t have nested fields, they represent the leaves of a schema. These are the built-in
scalar types in GraphQL:

• Int
• Float
• String
• Boolean
• ID

Some GraphQL implementations allow you to define custom scalar types. This means that you could
create custom scalars such as Date or JSON.
You can define a special kind of scalars using enum types. Enumerations are special scalars because
they are restricted to a fixed set of values.
This is how an enum looks like:

1 enum PinStatus {
2 DELETED
3 HIDDEN
4 VISIBLE
5 }

Input types behave almost exactly like objects. They can have fields inside of them, but the difference
is that those fields cannot have arguments and also cannot be of Object type.
This is how the custom PinInput type is defined:
2. Data modeling 30

1 input PinInput {
2 title: String!
3 link: String!
4 image: String!
5 }

GraphQL allows you to define Interface and Union types. They are useful when you want to return
an object which can be of several different types.
You can use interfaces when you have different types which share fields between them. A common
use case would be representing a User type.

1 interface Person {
2 id: String!
3 email: String!
4 pins: [Pin]
5 }
6
7 type User implements Person {
8 id: String!
9 email: String!
10 pins: [Pin]
11 }
12
13 type Admin implements Person {
14 id: String!
15 email: String!
16 pins: [Pin]
17 }

When you want to create a type that represents different types with no shared fields between them,
you must use a Union type. A typical operation that returns this type is a search:

1 union SearchResult = User | Admin | Pin


2
3 type Query {
4 # ...
5 search(text: String): [SearchResult]
6 }

This is what the complete version of schema.graphql looks like:


2. Data modeling 31

1 type Pin {
2 title: String!
3 link: String!
4 image: String!
5 id: String!
6 user_id: String!
7 }
8
9 input PinInput {
10 title: String!
11 link: String!
12 image: String!
13 }
14
15 interface Person {
16 id: String!
17 email: String!
18 pins: [Pin]
19 }
20
21 type User implements Person {
22 id: String!
23 email: String!
24 pins: [Pin]
25 }
26
27 type Admin implements Person {
28 id: String!
29 email: String!
30 pins: [Pin]
31 }
32
33 union SearchResult = User | Admin | Pin
34
35 type Query {
36 pins: [Pin]
37 pinById(id: String!): Pin
38 users: [User]
39 me: User
40 search(text: String): [SearchResult]
41 }
42
2. Data modeling 32

43 type Mutation {
44 addPin(pin: PinInput!): Pin
45 sendShortLivedToken(email: String!): Boolean
46 createLongLivedToken(token: String!): String
47 }

As you learned in the previous section, a schema is comprised of type definitions and resolvers. Now
that you know how type definitions look like, it’s time to learn about resolvers.

2.4 Resolvers
Resolvers are the functions that run every time a query requests a field. When a GraphQL
implementation receives a query, it runs the resolver for each field. If the resolver returns an Object
field, then GraphQL runs that field’s resolver function. When all resolvers return scalars, the chain
ends and the query receives its final JSON result.
Since GraphQL is not tied to any database technology, it leaves resolver implementation entirely up
to you. All functions in resolvers.js use a simple JS object that serves as a memory database, but
in the next chapter you will learn how to migrate to a Postgres database.
You can organize resolvers in any way you want, depending on your needs. The examples in this
book strive to keep resolver functions simple, and also separating database access with business
logic. This is a simple case of applying the good old Separation of Concerns⁷ pattern.
This is what resolvers.js looks like:

1 const {
2 addPin,
3 createShortLivedToken,
4 sendShortLivedToken,
5 createLongLivedToken,
6 createUser
7 } = require("./business-logic");
8
9 const database = {
10 users: {},
11 pins: {}
12 };
13
14 const resolvers = {
15 Query: {

⁷https://en.wikipedia.org/wiki/Separation_of_concerns
2. Data modeling 33

16 pins: () => Object.values(database.pins),


17 users: () => Object.values(database.users),
18 search: (_, { text }) => {
19 return [
20 ...Object.values(database.pins).filter(pin =>
21 pin.title.includes(text)
22 ),
23 ...Object.values(database.users).filter(user =>
24 user.email.includes(text)
25 )
26 ];
27 }
28 },
29 Mutation: {
30 addPin: async (_, { pin }, { user }) => {
31 const {
32 user: updatedUser,
33 pin: createdPin
34 } = await addPin(user, pin);
35 database.pins[createdPin.id] = createdPin;
36 database.users[user.id] = updatedUser;
37 return createdPin;
38 },
39 sendShortLivedToken: (_, { email }) => {
40 let user;
41 const userExists = Object.values(database.users).find(
42 u => u.email === user.email
43 );
44 if (userExists) {
45 user = userExists;
46 } else {
47 user = createUser(email);
48 database.users[user.id] = user;
49 }
50 const token = createShortLivedToken(user);
51 return sendShortLivedToken(email, token);
52 },
53 createLongLivedToken: (_, { token }) => {
54 return createLongLivedToken(token);
55 }
56 },
57 Person: {
2. Data modeling 34

58 __resolveType: person => {


59 if (person.admin) {
60 return "Admin";
61 }
62 return "User";
63 }
64 },
65 User: {
66 pins({ id }) {
67 return Object.values(database.pins).filter(
68 pin => pin.user_id === id
69 );
70 }
71 },
72 SearchResult: {
73 __resolveType: searchResult => {
74 if (searchResult.admin) {
75 return "Admin";
76 }
77 if (searchResult.email) {
78 return "User";
79 }
80 return "Pin";
81 }
82 }
83 };
84
85 module.exports = resolvers;

In this case, not all fields of Query and Mutation have a corresponding resolver. When a field does
not have a resolver, it will resolve as null. Of course this is just for demonstration purposes. Your
API clients would not be very happy with queries that always return null.
You can see that most of the logic in the fields of Query and Mutations come from the functions
in business-logic.js. The function bodies are mostly data access and calls to methods from the
business logic module.
Some of the types in resolvers.js have methods named __resolveType. This is a method that
makeExecutableSchema from graphql-tools uses. It determines the type of objects which are of
type Union or Interface.
You can try this example schema by opening your remixed example’s console and run node
queries.js. This script simulates a user who first creates an authentication token, and sends it
in order to add a new pin.
2. Data modeling 35

1 $ node queries
2 API Token:
3 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
4 {
5 "data": {
6 "addPin": {
7 "id": "f5220ee1-bfeb-48a0-be9f-c63d055b8139",
8 "title": "Hello world",
9 "link": "http://graphql.college/fullstack-graphql",
10 "image": "http://graphql.college/fullstack-graphql",
11 "user_id": "75c16079-b3ef-43f0-a352-ae03f2488baa"
12 }
13 }
14 }

Feel free to learn by modifying the different resolver functions and seeing how that changes the
final result. You can also create different queries, now that you know what queries and mutations
your schema exposes.

2.5 Summary
You learned how to create GraphQL schemas. You wrote type definitions using SDL, and resolvers
using Javascript. The schema you created in this chapter is accessible by scripts using graphql-js.
The next chapter will teach you how to create GraphQL HTTP APIs. You will add the different
layers that make up a GraphQL server on top of the GraphQL schema from this chapter. This API
will have several additional layers, like HTTP, database and authentication.
3. GraphQL APIs
The most common way of exposing a GraphQL schema is with an HTTP server. Building GraphQL
APIs is much more than just designing schemas. This chapter will teach you how to create robust,
layered GraphQL APIs.

Server

You will learn how to expose GraphQL schemas using Apollo Server. How to connect your resolvers
to a database. You will add email based authentication to your API. Finally you will learn how to
organize your source code based on features.
3. GraphQL APIs 37

Server layers

All stages in this chapter have a corresponding project, which you can remix to learn by practice.
Let’s start by learning how to create an API using Apollo Server.

3.1 Server
Apollo Server is an open source, spec-compliant GraphQL server. It is a production-ready, easy to
setup way of exposing GraphQL schemas, so HTTP clients can consume them.
3. GraphQL APIs 38

HTTP

You already created a schema using graphql-tools in the previous chapter, so exposing it with
Apollo Server is really straightforward.

1 const { ApolloServer } = require("apollo-server");


2
3 const schema = require("./schema");
4
5 const server = new ApolloServer({ schema });
6
7 server.listen().then(({ url }) => {
8 console.log(`🚀 Server ready at ${url}`);
9 });

That’s really it! With just a call to server.listen() you have a live GraphQL API. Remix the
following example project to create your own copy.
Remix server example⁸
⁸https://glitch.com/edit/#!/remix/pinapp-server
3. GraphQL APIs 39

Click the Show button, on the top left of the screen, to open a batteries included GraphQL client
called GraphQL Playground⁹. It is more than just a GraphQL client, it almost feels like an IDE. It has
query autocomplete, it has GraphQL schema documentation and it stores all your queries so you
can reuse them later.

GraphQL Playground

Now that you have deployed your GraphQL API, it’s time to add persistence using a database.

3.2 Database
GraphQL APIs can be backed up by any data source. They can use SQL databases, NoSQL, in-
memory databases or even use HTTP endpoints.
⁹https://github.com/prismagraphql/graphql-playground
3. GraphQL APIs 40

Database

In this chapter you will connect PinApp to a SQLite database using a database connector called
Knex¹⁰. Knex is a SQL query builder that can communicate with many SQL databases, like SQLite3,
MySQL, Postgres and more.
Remix the current iteration of PinApp so you can follow along the contents of this section with your
own copy of the project.
Remix database example¹¹

Remember to follow the getting started instructions on the project’s README

Since all database related interactions happen in resolvers.js, let’s start with the contents of that
file.
Retrieve a set of records using select(). For example this is how to get the list of pins.

¹⁰http://knexjs.org
¹¹https://glitch.com/edit/#!/remix/pinapp-database
3. GraphQL APIs 41

1 pins: () => database("pins").select(),

You can chain together Knex functions. For example you can filter results from a select() by
chaining its call with the where() function.

1 search: (_, { text }) => {


2 return Promise.all([
3 database("users")
4 .select()
5 .where("email", "like", `%${text}%`),
6 database("pins")
7 .select()
8 .where("title", "like", `%${text}%`)
9 ]);
10 };

Another useful function that knex provides is insert(). It allows you to create objects in your
database. This is how the addPin mutation looks like using insert.

1 addPin: async (_, { pin }, { token }) => {


2 const [user] = await authorize(token);
3 const { user: updatedUser, pin: createdPin } = await addPin(user, pin);
4 await database("pins").insert(createdPin);
5 return createdPin;
6 },

The file called database.js creates an instance of knex and exports it as a module. It is a simple file
that creates a knex instance with configuration values from a file called knexfile.js.

1 const database = require("knex")(require("./knexfile"));


2
3 module.exports = database;

Knex provides a CLI which provides several utilities for creating migrations, seeding databases, and
more. It reads configuration from knexfile.js. PinApp’s configuration file looks like this:
3. GraphQL APIs 42

1 module.exports = {
2 client: "sqlite3",
3 connection: {
4 filename: ".data/database.sqlite"
5 }
6 };

Glitch¹² allows you to persist data inside the .data folder. This is why database.sqlite
is located in that folder.

The final step you need in order to use a SQL file is to generate your database schema. Knex allows
you to generate migration files using its CLI.
Running npx knex migrate:make create_users_table creates a file called [date]_create_users_-
table.js inside the .migrations folder. This file exports two methods, up and down. These files are
placeholders, which you need to fill in with your specific needs. In this case, the user table needs to
have two fields, id and email. Both will have type string. The id field will be a primary key.

1 exports.up = function(knex) {
2 return knex.schema.createTable("users", function(table) {
3 table.string("id").primary();
4 table.string("email");
5 });
6 };
7
8 exports.down = function(knex) {
9 return knex.schema.dropTable("users");
10 };

There is another migration in the project you remixed, called [date]_create_pins_migration. It


defines five string fields: id, title, link, image and pin_id.

¹²https://glitch.com
3. GraphQL APIs 43

1 exports.up = function(knex) {
2 return knex.schema.createTable("pins", function(table) {
3 table.string("id").primary();
4 table.string("title");
5 table.string("link");
6 table.string("image");
7 table
8 .string("user_id")
9 .references("id")
10 .inTable("users")
11 .onDelete("CASCADE")
12 .onUpdate("CASCADE");
13 });
14 };
15
16 exports.down = function(knex) {
17 return knex.schema.dropTable("pins");
18 };

Running npm run setup-db will apply all database migrations. This script is defined in the scripts
key of package.json:

1 "setup-db": "knex migrate:latest"`

Teaching SQL is outside of the scope of this book, it needs a book on its own if you want to properly
learn it. Knex does a great job at interacting with SQL databases for Javascript users, and it has great
documentation¹³. Refer to it if you want to learn more about it.

3.3 Authentication
A common question when building GraphQL APIs is “Where to put authentication and authoriza-
tion?”. Should it be in the GraphQL layer? Database layer? Business logic? Even though the answer
depends on the context of what API you are building, a common way to solve this problem is to
put authentication and authorization in the business layer. Putting auth related code in the business
layer is Facebook’s approach¹⁴.
¹³http://knexjs.org
¹⁴https://dev-blog.apollodata.com/graphql-at-facebook-by-dan-schafer-38d65ef075af
3. GraphQL APIs 44

Business Logic

You can implement auth in several ways, that is entirely up to your needs. This section will teach
you how to add email based authentication to PinApp.
Email based authentication consists of providing an email input to your users. Once they submit
their email, you send them an authentication token inside a link to your app. If they enter your
app using a valid token, you can trust this user. Once they enter your site with a valid token, you
exchange that temporary token with a token with a longer expiration date.
The biggest advantage of this authentication system, as opposed to good old password-based
authentication, is that you don’t need to deal with passwords at all. Deciding not to store passwords
means you don’t have to take extreme security measures to keep them safe. It also means that your
users don’t have to deal with yet another site that asks them to create a new password.
3. GraphQL APIs 45

1 Feature: Email based authentication


2
3 Scenario: Send magic link
4 Given a user enters his email address
5 When he/she submits the form
6 Then he/she should receive an email with a link to the app that contains his\
7 token
8
9 Scenario: Verify email
10 Given a user receives an email with a magic link
11 When he/she clicks the link
12 Then he/she should see a loading screen
13 When the app verifies the token
14 Then he/she should see an email confirmation message

Speaking of GraphQL terms, both of these actions are mapped onto mutations in their correspond-
ing GraphQL schema, called sendShortLivedToken and createLongLivedToken. The first action
receives an email as argument, which is of type String. The second action receives a token and
returns the new token.

1 type Mutation {
2 # ...
3 sendShortLivedToken(email: String!): Boolean
4 createLongLivedToken(token: String!): String
5 }

Remix this project so you can follow the implementation of email based auth.
Remix email authentication example¹⁵
Now let’s analyze how the email-related resolvers look like.
The resolver for sendShortLivedToken should check if the user that corresponds to the email exists.
If it doesn’t exist, then it should insert it into the database. After this, it should create a token with
a short expiration time, and send it to the user’s email.

¹⁵https://glitch.com/edit/#!/remix/pinapp-email-authentication
3. GraphQL APIs 46

1 sendShortLivedToken: async (_, { email }) => {


2 let user;
3 const userExists = await database("users")
4 .select()
5 .where({ email });
6 if (userExists.length) {
7 user = userExists[0];
8 } else {
9 user = createUser(email);
10 await database("users").insert(user);
11 }
12 const token = createShortLivedToken(user);
13 return sendShortLivedToken(email, token);
14 };

This resolver uses two functions from business-logic.js, createShortLivedToken and send-
ShortLivedToken.

The first one creates a token using the jsonwebtoken NPM package’s sign¹⁶ function. This token
will have an expiration time of five minutes.

1 const createShortLivedToken = ({ email, id }) => {


2 return jsonwebtoken.sign(
3 { id, email },
4 process.env.SECRET,
5 {
6 expiresIn: "5m"
7 }
8 );
9 };

The second function, sendShortLivedToken, uses a function defined in email.js called sendMail.

¹⁶https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback
3. GraphQL APIs 47

1 const sendShortLivedToken = (email, token) => {


2 return sendMail({
3 from: '"Julian" <julian@graphql.college>',
4 to: email,
5 text: `${process.env.APP_URL}/verify?token=${token}`,
6 html: `<a href="${
7 process.env.APP_URL
8 }/verify?token=${token}" target="_blank">Authenticate</a>`,
9 subject: "Auth token"
10 });
11 };

To send mails, you will use the nodemailer package. This library allows you to send emails through
an SMTP server. The easiest way to create an email server for development purposes is with
Ethereal¹⁷. This is a fake email service developed by the creators of Nodemailer, and it is a super
easy way to create dev SMTP services. Of course, if you want to send actual emails, you should use
a real SMTP service. Sendgrid¹⁸ has a great free plan.

1 const nodemailer = require("nodemailer");


2
3 const transporter = nodemailer.createTransport({
4 host: "smtp.ethereal.email",
5 port: 587,
6 auth: {
7 user: process.env.MAIL_USER,
8 pass: process.env.MAIL_PASSWORD
9 }
10 });
11
12 function sendMail({ from, to, subject, text, html }) {
13 const mailOptions = {
14 from,
15 to,
16 subject,
17 text,
18 html
19 };
20 return new Promise((resolve, reject) => {
21 transporter.sendMail(mailOptions, (error, info) => {
22 if (error) {

¹⁷https://ethereal.email/messages
¹⁸https://sendgrid.com/
3. GraphQL APIs 48

23 return reject(error);
24 }
25 resolve(info);
26 });
27 });
28 }
29
30 module.exports = sendMail;

The implementation of createLongLivedToken is much simpler than sendShortLivedToken. It uses


a function from business-logic.js that verifies the token it receives as argument. If that token is
valid, it creates a token with an expiration date of thirty days.

1 const createLongLivedToken = token => {


2 try {
3 const { id, email } = jsonwebtoken.verify(
4 token,
5 process.env.SECRET
6 );
7 const longLivedToken = jsonwebtoken.sign(
8 { id, email },
9 process.env.SECRET,
10 { expiresIn: "30 days" }
11 );
12 return Promise.resolve(longLivedToken);
13 } catch (error) {
14 console.error(error);
15 throw error;
16 }
17 };

Go ahead and configure your remixed project with your Ethereal account. Once you have setup
everything, hop into GraphQL Playground by clicking the “Show” button and authenticate using
your email (or any email actually, Ethereal intercepts all of them :D).

3.4 File organization


This section will teach you how to use graphql-import to organize Node.js GraphQL APIs by
features. GraphQL import allows you to import and export type definitions in GraphQL SDL.
Up to this point, all files in the sample repository are organized by role, but this approach does
not scale well for large projects. Right now, resolvers are in resolvers.js, type definitions are in
3. GraphQL APIs 49

schema.graphql and all business logic is in business-logic.js. As the projects grows bigger and
bigger, this three files will end up becoming too large and unmanageable.
The project’s current file structure looks like this:

Current file structure

You are going to split schema.graphql, resolvers.js and business-logic.js into three features:
authentication, pins and search. The final directory structure will be the following:
3. GraphQL APIs 50

Final file structure

Remix the project if you want to see how the final version looks like.
Remix file organization example¹⁹
The main entry point of the GraphQL schema will still be schema.graphql. The difference is that
it will not contain any type definitions, it will import all types from the schema.graphql of every
feature folder. The main schema will import the rest of the schemas using the import statement that
graphql-import provides. Its syntax is # import * from "module-name.graphql".

1 # import * from "authentication/schema.graphql"


2 # import * from "pins/schema.graphql"
3 # import * from "search/schema.graphql"

This way of importing GraphQL SDL is possible because schema.js loads schema.graphql with the
following snippet:

¹⁹https://glitch.com/edit/#!/remix/pinapp-files
3. GraphQL APIs 51

1 const { importSchema } = require("graphql-import");


2
3 const typeDefs = importSchema("schema.graphql");
4 // ...

Similarly to the schema, the main entry point for all resolvers will remain the same. It will still
be resolvers.js. NodeJS already provides a way to import and export files using require, so you
don’t need any additional library. Even though this file does not need any additional library to
import modules, it uses a library to merge the resolvers it imports. You could use pure Javascript to
achieve this, but lodash.merge is a nice way to merge many Javascript objects.

1 const merge = require("lodash.merge");


2
3 const searchResolvers = require("./search/resolvers.js");
4 const authenticationResolvers = require("./authentication/resolvers.js");
5 const pinsResolvers = require("./pins/resolvers.js");
6
7 const resolvers = merge(
8 searchResolvers,
9 authenticationResolvers,
10 pinsResolvers
11 );
12
13 module.exports = resolvers;

To finish the file structure changes, split business-logic.js, resolvers.js and schema.graphql.
Split business-logic.js into authentication/index.js and pins/index.js. Split resolvers.js
into authentication/resolvers.js, pins/resolvers.js and search/resolvers.js. Finally split
schema.graphql into the three new folders.

That’s it! Using a feature based file structure is a scalable way of organizing code. It may be overkill
for small projects, but it pays off in big ones.

3.5 Summary
You learned how to create a GraphQL API using Apollo Server. Starting from just a GraphQL
schema, you learned how to wrap that schema with an HTTP layer using ApolloServer. You added
a database layer using Knex, email based authentication with Nodemailer. In the last step, you
organized your project by features using GraphQL Import.
The next chapter will teach you how to create GraphQL clients using Apollo Client. You will learn
how to implement a frontend in React that communicates with the GraphQL API you just created.
4. GraphQL clients
In this chapter you will learn how to build a GraphQL client using React and Apollo GraphQL.

Client

You will start with a bare-bones React application, and transform it step-by-step into a GraphQL
powered Pinterest clone. The first step will be making an app with all state stored in the client.
After that you will learn how to add a GraphQL library called Apollo Client. Finally, you will learn
how to easily fetch and store data in the GraphQL server using React Apollo’s Query and Mutation
components.
Let’s start with the initial version of PinApp.
4. GraphQL clients 53

4.1 Initial React client


The first version of PinApp’s client consists of a single App component that renders a div that says
“PinApp”. The project’s setup is pretty standard, since it’s based on the super popular create-react-
app²⁰. Here is the complete source code for src/App.js:

1 import React from "react";


2
3 export default class App extends React.Component {
4 render() {
5 return <div>PinApp</div>;
6 }
7 }

Remix this project to create your own copy of it:


Remix initial React client²¹
The next section will teach you how to create a client side version of PinApp.

4.2 Client side state


This section will teach you how to create a working, client-side only version of PinApp. To achieve
this, you will use a library called pinapp-components²². This library exports a Container component,
a Nav component and also five Page components, PinListPage, LoginPage, VerifyPage, AddPinPage
and ProfilePage. To get to know all of these components, see them live in the playground²³.
These components expose a well defined API via their props. This customization enables you to use
them to create a version of PinApp using only local state and passing it via props. Using the same
components, but passing different props, you will create a version of PinApp backed by the GraphQL
API you created in the previous chapter.
Following is a brief description of all components.
Container sets up PinApp’s styling and routing configuration. Render it as the first in your
component hierarchy. It only receives children as props.
Nav renders a list of actions as the app’s footer. Actions are links to /, /upload-pin and /profile.

The rest of the components define a page each.


²⁰https://github.com/facebook/create-react-app
²¹https://glitch.com/edit/#!/remix/pinapp-initial
²²https://github.com/GraphQLCollege/pinapp-components
²³https://graphqlcollege.github.io/pinapp-components
4. GraphQL clients 54

PinListPage renders a list of pins in the / URL. It receives just one argument, pins. pins is an array
that contains a list of pins. Pins must be object with title, image and link properties. All three
properties must be Strings.

PinListPage

LoginPage sets up the /login URL. It receives only one prop, authenticate. It is a function that will
run when the user clicks “login”. This function receives a string as an argument, which contains an
email that the user entered.
4. GraphQL clients 55

LoginPage

VerifyPage sets up a screen in the /verify?token={token} URL. This component receives one prop
called verify. It is a function that will run when the component mounts. This function passes a
string argument which contains the token that the user entered in the URL’s query string.
4. GraphQL clients 56

VerifyPage

AddPinPage renders a form to create a pin in the /upload-pin URL. This component receives two
props, authenticated and addPin. authenticated is a boolean. addPin is a function that will run
when the user clicks “Save”. It receives a pin object as argument.
4. GraphQL clients 57

AddPinPage

The last page component is ProfilePage. It shows the users’ profile in the /profile URL. It
receives three props, user, authenticated and logout. user is an object with an email property.
authenticated is a boolean. logout is a function that will run when the user clicks “Logout”. It does
not receive any arguments.
4. GraphQL clients 58

ProfilePage

Now let’s talk about how state management. The app will have three keys in state. It will hold
an array of pins, a boolean called authenticated and a user object. The App component will
have three functions that directly modify this state, called addPin, verify and logout. To simulate
authentication, it will have an authenticate function that always authenticates the user. This
App component will pass down its state and functions as props to the components from pinapp-
components.

The first thing you need to do is add "pinapp-components": "^1.0.1" to the "dependencies" key
in your package.json.
After adding pinapp-components as a dependency, modify src/App.js to look like this:

1 import React from "react";


2 import {
3 Container,
4 Nav,
5 PinListPage,
6 AddPinPage,
7 LoginPage,
8 VerifyPage,
9 ProfilePage
4. GraphQL clients 59

10 } from "pinapp-components";
11
12 export default class App extends React.Component {
13 state = { pins: [], authenticated: false, user: null };
14 addPin = pin => {
15 this.setState(({ pins }) => ({
16 pins: pins.concat([pin])
17 }));
18 };
19 verify = () => {
20 return success().then(token =>
21 this.setState({
22 authenticated: true,
23 user: { email: "name@example.com" }
24 })
25 );
26 };
27 authenticate = () => {
28 return Promise.resolve({});
29 };
30 logout = () => {
31 this.setState({ authenticated: false, user: null });
32 };
33 render() {
34 return (
35 <Container>
36 <PinListPage pins={this.state.pins} />
37 <AddPinPage
38 authenticated={this.state.authenticated}
39 addPin={this.addPin}
40 />
41 <LoginPage authenticate={this.authenticate} />
42 <VerifyPage verify={this.verify} />
43 <ProfilePage
44 authenticated={this.state.authenticated}
45 user={this.state.user}
46 logout={this.logout}
47 />
48 <Nav authenticated={this.state.authenticated} />
49 </Container>
50 );
51 }
4. GraphQL clients 60

52 }
53
54 function success() {
55 return wait(1000).then(() => "long-lived-token");
56 }
57
58 function wait(time) {
59 return new Promise((resolve, reject) => {
60 setTimeout(resolve, time);
61 });
62 }

Remix this step’s application²⁴ if you got stuck in some way.


Congratulations! You have got a working version of PinApp. Too bad that all pins get lost when you
refresh the application. This happens because the app stores everything in-memory. The next couple
of sections will teach you how to connect PinApp with your GraphQL API.

4.3 Apollo Client


In this section you will add Apollo Client²⁵ to PinApp’s frontend. You will setup Apollo Client, point
it to the API you created in the previous chapter, and send a query to that endpoint.
Apollo Client is a GraphQL Client that provides advanced data loading to Javascript applications. It
provides wrappers to many popular Javascript frameworks, like React, React Native, Angular, Vue.js,
it also provides Native iOS and Android versions.
To install it, you need to add three dependencies to package.json. These dependencies are apollo-
boost",, graphql and graphql-tag. This is what your "dependencies" key should look like:

1 "dependencies": {
2 "react-scripts": "^1.1.4",
3 "react": "^16.3.2",
4 "react-dom": "^16.3.2",
5 "pinapp-components": "^1.0.1",
6 "apollo-boost": "^0.1.6",
7 "graphql": "^0.13.2",
8 "graphql-tag": "^2.9.2"
9 },

²⁴https://glitch.com/edit/#!/remix/pinapp-client-side-state
²⁵https://www.apollographql.com/client
4. GraphQL clients 61

Now it’s time to setup Apollo Client in src/App.js. Import apollo-boost as ApolloClient and
import graphql-tag as gql.
After that, create a new instance of ApolloClient, pass it an object with an uri key pointing to your
GraphQL API URL, and assign it to a variable called client.

1 import ApolloClient from "apollo-boost";


2 import gql from "graphql-tag";
3
4 const client = new ApolloClient({
5 uri: process.env.REACT_APP_API_URL
6 });

Remember to add REACT_APP_API_URL=https://pinapp-files.glitch.me to the .env file, pointing


to your API URL.
Once you have created an instance of Apollo Client, you can use it to send queries and mutations
to your API. Let’s use the query method from Apollo Client to fetch the list of pins from the API.
This method receives an object with a query key. Inside that key you can send queries using the gql
function from graphql-tag.
The gql function receives a string written in SDL and transforms it into a Javascript object.
client.query accepts this Javascript object. Note that client.query throws an error if you pass
a string directly, without using gql.
Another handy feature of gql is that many IDEs add syntax highlighting to gql calls. Unfortunately,
at the moment Glitch does not support this feature. But who knows, maybe in the future it supports
gql syntax highlighting. And maybe you are reading this in the future and enjoying beautiful
GraphQL queries in our Glitch’s code examples.
Add the following componentDidMount function to the App component:

1 componentDidMount() {
2 client
3 .query({
4 query: gql`
5 {
6 pins {
7 title
8 image
9 link
10 }
11 }
12 `
13 })
4. GraphQL clients 62

14 .then(result => this.setState({ pins: result.data.pins }));


15 }

You can click here²⁶ to remix this step’s version of PinApp. Remember to edit REACT_APP_API_URL
in .env.
As you can see, it’s really easy to use Apollo Client to communicate with GraphQL APIs. But Apollo
Client provides an even better way of connecting your React application with a GraphQL API. It
provides a library called React Apollo, which lets you collocate components with data by placing
queries alongside your components. The next section will teach you how to setup React Apollo to
interact with PinApp’s API.

4.4 React Apollo


React Apollo is a library that lets you declaratively specify your component’s data requirements.
This means that your components can specify what data they need, instead of how to fetch that
data. You achieve this by placing GraphQL queries in your components, and delegating how to
fetch that data to Apollo Client.

4.5 Query component


A component that React Apollo provides is called Query. It receives one prop, called query, which
accepts a GraphQL query. This component also receives a function as a child. Functions as child is
a React pattern that lets you pass data from parents to children. Many of React Apollo’s component
use this pattern to pass down information. You can read more about passing functions to component
in React’s official documentation²⁷.
The Query component passes down an object to its children. It contains three keys, loading, error
and data. loading is a boolean that is true when the component started fetching data, but has not
finished yet. error is an object that contains GraphQL errors, if any. data is an object that contains
the GraphQL API’s response.
Let’s use this Query component. Create a file called src/PinListPage.js. If loading is true, it will
return the Spinner from pinapp-components. If error is defined, it will display a div with the text
“Error”. And if data is defined, it will return PinListPage from pinapp-components.

²⁶https://glitch.com/edit/#!/remix/pinapp-apollo-client
²⁷https://reactjs.org/docs/faq-functions.html
4. GraphQL clients 63

1 import React from "react";


2 import { Query } from "react-apollo";
3 import { PinListPage, Spinner } from "pinapp-components";
4
5 import { LIST_PINS } from "./queries";
6
7 class PinListPageContainer extends React.Component {
8 render() {
9 return (
10 <Query query={LIST_PINS}>
11 {({ loading, error, data }) => {
12 if (loading) {
13 return (
14 <Spinner
15 accessibilityLabel="Loading pins"
16 show
17 />
18 );
19 }
20 if (error) {
21 return <div>Error</div>;
22 }
23 return <PinListPage pins={data.pins} />;
24 }}
25 </Query>
26 );
27 }
28 }
29
30 export default PinListPageContainer;

You may have noticed that PinListPage.js references LIST_PINS from queries. Create this new
file called src/queries.js. It will contain all queries that PinApp needs. Paste the following code
in this new file.
4. GraphQL clients 64

1 import gql from "graphql-tag";


2
3 export const ADD_PIN = gql`
4 mutation AddPin($pin: PinInput!) {
5 addPin(pin: $pin) {
6 title
7 link
8 image
9 }
10 }
11 `;
12
13 export const LIST_PINS = gql`
14 {
15 pins {
16 id
17 title
18 link
19 image
20 user_id
21 }
22 }
23 `;
24
25 export const CREATE_LONG_LIVED_TOKEN = gql`
26 mutation CreateLongLivedToken($token: String!) {
27 createLongLivedToken(token: $token)
28 }
29 `;
30
31 export const CREATE_SHORT_LIVED_TOKEN = gql`
32 mutation CreateShortLivedToken($email: String!) {
33 sendShortLivedToken(email: $email)
34 }
35 `;
36
37 export const ME = gql`
38 {
39 me {
40 email
41 }
42 }
4. GraphQL clients 65

43 `;

4.6 Apollo Provider


In order to use React Apollo’s Query component, you need to wrap your application with a
component named ApolloProvider. It receives an instance of ApolloClient, and provides querying
capabilities to Query components inside the component hierarchy.

1 import { ApolloProvider } from "react-apollo";

Remove PinListPage from the list of imports from pinapp-components and import it from the file
you just created.

1 import PinListPage from "./PinListPage";

Put ApolloProvider as the first component in App’s render. Also remove pins prop from PinList-
Page because it does not need it anymore.

1 <ApolloProvider client={client}>
2 <Container>
3 <PinListPage />
4 {/* ... */}
5 </Container>
6 </ApolloProvider>

Finally, you can now delete the componentDidMount function, because you load the list of pins using
Query.
You can start seeing the benefits of declarative fetching using React Apollo, compared to fetching
data in React’s lifecycle methods, like componentDidMount.

4.7 Mutation component


React Apollo provides a component called Mutation that allows you to collocate mutations with
React components.
It works similarly to Query, it receives a query as a prop and it also receives a function as its child.
It receives a GraphQL query, in this case that property is called mutation instead of query.
Similarly to Query, it receives a function as its child. The main difference is that this component
passes down a function as its first argument, instead of an object with data, loading and error. It
4. GraphQL clients 66

actually passes that object as a second argument, it will contain the data returned by the mutation.
Calling this function will send a mutation to the GraphQL API configured in ApolloProvider.
Let’s use React Apollo’s Mutation. Create a file called src/LoginPage.js. Inside it, create a React
class Component that returns Mutation in its render method. Place a function as a child of Mutation.
This function will receive an argument called createShortLivedToken. LoginPage will receive
a function in its authenticate property that calls createShortLivedToken when the user clicks
“Login”.

1 import React from "react";


2 import { Mutation } from "react-apollo";
3 import { LoginPage } from "pinapp-components";
4
5 import { CREATE_SHORT_LIVED_TOKEN } from "./queries";
6
7 class LoginPageContainer extends React.Component {
8 render() {
9 return (
10 <Mutation mutation={CREATE_SHORT_LIVED_TOKEN}>
11 {createShortLivedToken => (
12 <LoginPage
13 authenticate={email =>
14 createShortLivedToken({
15 variables: { email }
16 })
17 }
18 />
19 )}
20 </Mutation>
21 );
22 }
23 }
24
25 export default LoginPageContainer;

To use this component, remove LoginPage from the list of pinapp-components import in src/App.js
and import it from the file you just created.
After sending a mutation to the server, you usually want to update the data in your app. For example,
after adding a pin, you want to update the list of pins in your application. An easy way to achieve this
is using another property from Mutation, called refetchQueries. It receives an array that contains
the list of GraphQL queries to send after the mutation finishes.
To see refetchQueries in use, create a file called src/AddPinPage.js with the following contents:
4. GraphQL clients 67

1 import React from "react";


2 import { Mutation } from "react-apollo";
3
4 import { AddPinPage } from "pinapp-components";
5 import { ADD_PIN, LIST_PINS } from "./queries";
6
7 class AddPinPageContainer extends React.Component {
8 render() {
9 return (
10 <Mutation mutation={ADD_PIN}>
11 {addPin => (
12 <AddPinPage
13 authenticated={this.props.authenticated}
14 addPin={pin =>
15 addPin({
16 variables: { pin },
17 refetchQueries: [{ query: LIST_PINS }]
18 })
19 }
20 />
21 )}
22 </Mutation>
23 );
24 }
25 }
26
27 export default AddPinPageContainer;

Another useful prop that Mutation receives is called update. It is a function that gets called once your
mutation finishes. You can use it to update React Apollo’s internal state after a mutation finishes. In
this case you are going to use it to get access to the token that the createLongLived query returns.
Create a file called src/VerifyPage.js and place the following code in it.

1 import React from "react";


2 import { Mutation } from "react-apollo";
3 import { VerifyPage } from "pinapp-components";
4
5 import { CREATE_LONG_LIVED_TOKEN, ME } from "./queries";
6
7 class VerifyPageContainer extends React.Component {
8 render() {
9 return (
4. GraphQL clients 68

10 <Mutation
11 mutation={CREATE_LONG_LIVED_TOKEN}
12 update={(cache, { data }) => {
13 if (data && data.createLongLivedToken) {
14 this.props.onToken(data.createLongLivedToken);
15 }
16 }}
17 >
18 {createLongLivedToken => (
19 <VerifyPage
20 verify={shortLivedToken =>
21 createLongLivedToken({
22 variables: {
23 token: shortLivedToken
24 },
25 refetchQueries: [{ query: ME }]
26 })
27 }
28 />
29 )}
30 </Mutation>
31 );
32 }
33 }
34
35 export default VerifyPageContainer;

The App component will use onToken to update its state with the authentication token.

1 <VerifyPage
2 onToken={token => {
3 localStorage.setItem("token", token);
4 this.setState({ token });
5 }}
6 />

The final file you need to create is src/ProfilePage.js. This file does not contain any new API that
you need to know. It uses Query, just like PinListPage. It uses the ME query from queries.js. This
is what this file looks like:
4. GraphQL clients 69

1 import React from "react";


2 import { Query } from "react-apollo";
3 import { ProfilePage } from "pinapp-components";
4
5 import { ME } from "./queries";
6
7 class ProfilePageContainer extends React.Component {
8 render() {
9 if (!this.props.authenticated) {
10 return (
11 <ProfilePage
12 authenticated={this.props.authenticated}
13 logout={this.props.logout}
14 user={null}
15 />
16 );
17 }
18 return (
19 <Query query={ME}>
20 {({ loading, error, data }) => {
21 return (
22 <ProfilePage
23 authenticated={this.props.authenticated}
24 logout={this.props.logout}
25 user={{
26 email:
27 data && data.me ? data.me.email : null
28 }}
29 />
30 );
31 }}
32 </Query>
33 );
34 }
35 }
36
37 export default ProfilePageContainer;

The last step you need to take before reaching the final version of PinApp is replacing src/App.js
with the following code:
4. GraphQL clients 70

1 import React from "react";


2 import ApolloClient from "apollo-boost";
3 import { ApolloProvider } from "react-apollo";
4 import { Container, Nav } from "pinapp-components";
5
6 import PinListPage from "./PinListPage";
7 import LoginPage from "./LoginPage";
8 import VerifyPage from "./VerifyPage";
9 import AddPinPage from "./AddPinPage";
10 import ProfilePage from "./ProfilePage";
11
12 const client = new ApolloClient({
13 uri: process.env.REACT_APP_API_URL,
14 request: operation => {
15 if (this.state.token) {
16 operation.setContext({
17 headers: { Authorization: this.state.token }
18 });
19 }
20 }
21 });
22
23 export default class App extends React.Component {
24 state = {
25 token: null
26 };
27 componentDidMount() {
28 const token = localStorage.getItem("token");
29 if (token) {
30 this.setState({ token });
31 }
32 }
33 logout = () => {
34 localStorage.removeItem("token");
35 this.setState({ token: null });
36 };
37 render() {
38 return (
39 <ApolloProvider client={client}>
40 <Container>
41 <PinListPage />
42 <AddPinPage authenticated={!!this.state.token} />
4. GraphQL clients 71

43 <LoginPage />
44 <VerifyPage
45 onToken={token => {
46 localStorage.setItem("token", token);
47 this.setState({ token });
48 }}
49 />
50 <ProfilePage
51 authenticated={!!this.state.token}
52 logout={this.logout}
53 />
54 <Nav authenticated={!!this.state.token} />
55 </Container>
56 </ApolloProvider>
57 );
58 }
59 }

Remix²⁸ the final version of PinApp’s client to see what it looks like.

4.5 Summary
You created a version of PinApp using only local state, learned how to use Apollo Client and React
Apollo to connect this app with a GraphQL API. You achieved this using React Apollo’s Query and
Mutation to easily send queries and mutations.

The next chapter will teach you how to add real time functionality to apps using GraphQL
Subscriptions, which let GraphQL APIs push data to clients.
²⁸https://glitch.com/edit/#!/remix/pinapp-react-apollo
5. Subscriptions
GraphQL servers can provide a way for clients to fetch data in response to server-sent events. This
enables GraphQL powered applications to push data to users in response to events.
For example, you could use Subscriptions to send notifications to users when another user creates
new pins.

Subscriptions

This chapter will teach you how to implement subscriptions, both on your GraphQL API and
frontend.
5. Subscriptions 73

5.1 Server side subscriptions


Subscriptions are implemented as a persisting connection between server and client, as opposed
to queries and mutations, which are implemented as request/response actions. This means that
Subscriptions use Websockets as their transport layer, instead of HTTP.
To implement server-side subscriptions you need to declare a top-level Subscription type, implement
a resolver for each of its fields, and finally wire up a PubSub system to handle events.

5.2 PubSub systems


GraphQL subscriptions implementations require you to setup a PubSub adapter, but you are not tied
to any particular system. You can use an in memory PubSub, Redis, RabbitMQ, Postgres and more.
This is a list of the different PubSub implementations²⁹.
Using Postgres as a PubSub system is a great way of keeping your API simple. Postgres can serve
both as a database, and as an event system.
Since we are already using Knex to handle database interactions, migrating from SQLite3 to Postgres
will be straightforward. Let’s prepare your API for subscriptions by migrating its database to
Postgres.
The migration consists on creating a Postgres database, and pointing knex configuration to its URL,
instead of pointing it to a SQLite file.
Install the pg library by adding it to package.json’s dependencies.

1 "dependencies": {
2 // ...
3 "pg": "^7.4.3"
4 }

Replace all the code in knexfile.js with this:

²⁹https://github.com/apollographql/graphql-subscriptions#pubsub-implementations
5. Subscriptions 74

1 const pg = require("pg");
2
3 // Hack required to support connection strings and ssl in knex
4 // https://github.com/tgriesser/knex/issues/852#issuecomment-229502678
5 pg.defaults.ssl = true;
6
7 let connection = process.env.DATABASE_URL;
8
9 module.exports = {
10 client: "pg",
11 connection
12 };

Now, create a Postgres database. We recommend creating a Heroku Postgres³⁰ databases, because
they are easy to create. But you can create a Postgres instance in the way that you feel most
comfortable.
To create a Postgres database using Heroku, you need to create an account. After that, go to
https://dashboard.heroku.com/apps and create a new app. Once you create the app, provision
an add-on called Heroku Postgres by navigating to your app’s resources page. You need to copy
this database’s URL by going to “Settings” -> “Config Vars” and copying the value of DATABASE_URL.
Once you have the database url, place it inside your project’s .env as DATABASE_URL=url_that_-
you_just_copied. Run npm run setup-db inside your project’s console to verify your database
connection and set up all migrations.
Remix this step’s project³¹ if you got stuck somewhere along the way.
Database migration finished! You are now ready to implement server side subscriptions using
Postgres as a PubSub system.

5.3 Implementing server side Subscriptions


You will implement a way for clients to receive events every time a new pin is added. This event
allows clients features like updating the pins list in real time, or showing a notification every time
another user creates a pin.
Add a new Subscription type to pins/schema.graphql. It will have a single field called pinAdded,
which will return a Pin.

³⁰https://www.heroku.com/postgres
³¹https://glitch.com/edit/#!/remix/pinapp-postgres
5. Subscriptions 75

1 # ...
2 type Subscription {
3 pinAdded: Pin
4 }

After that, add a new key called Subscription to the resolvers list in pins/resolvers.js. Add a
type resolver for pinAdded, which will be an object with a subscribe key. Subscriptions resolvers are
not functions, like Query or Mutation resolvers, they are objects with a subscribe key that returns
AsyncIterable.

What is an AsyncIterable? Asynchronous iteration is a data access protocol, a way to iterate


through asynchronous data sources. It is an ECMAScript proposal³², which means it has a high
chance of becoming a part of the language. GraphQL.js subscriptions use them because they will be
a part of the Javascript standard eventually³³.
Anyway, you need to return an AsyncIterable object in subscriptions’ subscribe functions. All
subscription compatible PubSub implementations provide a method called asyncIterator which
receives an event name and return an AsyncIterable.

1 Subscription: {
2 pinAdded: {
3 subscribe: () => {
4 return pubsub.asyncIterator("pinAdded");
5 };
6 }
7 }

Install the Postgres subscriptions PubSub by adding the following line to package.json’s dependen-
cies list:

1 "dependencies": {
2 "graphql-postgres-subscriptions": "^1.0.1"
3 }

After adding this new dependency, create a new instance of it and assign it to a new variable called
pubsub.

³²https://github.com/tc39/proposal-async-iteration
³³https://github.com/graphql/graphql-js/issues/1135#issuecomment-350928960
5. Subscriptions 76

1 const pubsub = new PostgresPubSub({


2 connectionString: `${process.env.DATABASE_URL}?ssl=true`
3 });

When a new pin is created, you can fire an event to notify all users of this new pin.

1 pubsub.publish("pinAdded", { pinAdded: createdPin });

This is what the new Subscription resolver from pins/resolvers.js looks like:

1 const {
2 PostgresPubSub
3 } = require("graphql-postgres-subscriptions");
4
5 const { addPin } = require("./index");
6 const { verify, authorize } = require("../authentication");
7 const database = require("../database");
8
9 const pubsub = new PostgresPubSub({
10 connectionString: `${process.env.DATABASE_URL}?ssl=true`
11 });
12
13 const resolvers = {
14 Query: {
15 pins: () => database("pins").select()
16 },
17 Mutation: {
18 addPin: async (_, { pin }, { token }) => {
19 const [user] = await authorize(database, token);
20 const {
21 user: updatedUser,
22 pin: createdPin
23 } = await addPin(user, pin);
24 await database("pins").insert(createdPin);
25 pubsub.publish("pinAdded", { pinAdded: createdPin });
26 return createdPin;
27 }
28 },
29 Subscription: {
30 pinAdded: {
31 subscribe: () => {
32 return pubsub.asyncIterator("pinAdded");
5. Subscriptions 77

33 }
34 }
35 }
36 };
37
38 module.exports = resolvers;

The final changes you need to make are in src/server.js. You need to add subscriptions: true
to the Apollo Server constructor. You also need to check for the existence of req and req.headers,
because subscriptions don’t send a req object.

1 const { ApolloServer } = require('apollo-server');


2
3 const schema = require('./schema');
4
5 const server = new ApolloServer({
6 schema,
7 context: async ({ req }) => {
8 const context = {};
9 if (req && req.headers && req.headers.authorization) {
10 context.token = req.headers.authorization;
11 }
12 return context;
13 },
14 subscriptions: true
15 });
16
17 server.listen().then(({ url }) => {
18 console.log(`🚀 Server ready at ${url}`);
19 });

Remix³⁴ if you need a working version of the subscriptions example.


Congratulations! You just implemented server side subscriptions. Now head over to your project’s
GraphQL Playground by clicking “Show”.
Complete the authentication process by sending a sendShortLivedToken mutation. Copy the token
you received in your email inbox, and send it as a token param in createLongLivedToken. Place the
result of the last mutation as an “Authorization” header.

³⁴https://glitch.com/edit/#!/remix/pinapp-subscriptions
5. Subscriptions 78

1 {
2 "Authorization": "eyJhbGciOiJIUzI..."
3 }

Keep this tab open, and create a new tab pointing to your GraphQL Playground. Create a new
subscription query and press the play button. You will see a loading screen that says “Listening…”.

1 subscription {
2 pinAdded {
3 title
4 id
5 }
6 }

Go back to your first mutation, paste the following AddPin mutation.

1 mutation AddPin($pin: PinInput!) {


2 addPin(pin: $pin) {
3 title
4 }
5 }

And fill the pin variable argument in the “Query Variables” section.

1 {
2 "pin": {
3 "title": "Hello subscriptions!",
4 "link": "https://pinapp-subscriptions.glitch.me/",
5 "image": "https://pinapp-subscriptions.glitch.me/"
6 }
7 }

You should see the created pin data instead of “Loading…”.


5. Subscriptions 79

1 {
2 "data": {
3 "pinAdded": {
4 "title": "Hello subscriptions!",
5 "id": "cce1efda-b851-4272-b840-9aefbb84097f"
6 }
7 }
8 }

The next section will show you how to send subscriptions with React Apollo.

5.4 Client side subscriptions


Apollo client supports GraphQL subscriptions. Because subscriptions is an advanced feature of
GraphQL, creating a client that supports subscriptions takes a little more effort than implementing
a simple client. Instead of creating an instance of "apollo-boost", you will create an instance of
the more configurable ApolloClient from "apollo-client".
Setting up ApolloClient from "apollo-client" requires a bit more knowledge about the implemen-
tation details of Apollo Client than using apollo-boost, more specifically knowledge about its cache
and link properties.
Apollo client manages the data returned from GraphQL queries in a cache. As you may know, this
library does much more than just providing a nice interface for interacting with GraphQL servers.
It provides advanced data management features like caching, pagination, prefetching and more. It
stores all information in a cache. This cache is configurable, you can store items in memory, in a
redux store, and more.
The network layer of Apollo client is called Apollo link. Links direct where your data goes and where
it comes from. You can use Apollo link to swap your HTTP layer with a Websockets layer, or even
use mocks instead of network calls.
You will learn how to migrate away from Apollo boost to the configurable Apollo client in the next
section.

5.5 Apollo boost migration


This is what client initialization looks like with Apollo Boost.
5. Subscriptions 80

1 const client = new ApolloClient({


2 uri: process.env.REACT_APP_API_URL,
3 request: operation => {
4 if (this.state.token) {
5 operation.setContext({
6 headers: { Authorization: this.state.token }
7 });
8 }
9 }
10 });

Apollo client needs you to explicitly set its data store by setting a cache, and configure the network
layer with links. You will use apollo-link-http to point HTTP requests to your API. To simulate
Apollo Boost error handling, you will setup apollo-link-error. Finally, you need the app to keep
providing dynamic request interceptors in order to add the authentication token to every request.
You will achieve this creating a custom instance of ApolloLink.
Install the new dependencies to package.json. Remove apollo-boost and add the following
dependencies:

1 "dependencies": {
2 // ...
3 "apollo-client": "^2.3.1",
4 "apollo-cache-inmemory": "^1.2.1",
5 "apollo-link-http": "^1.5.4",
6 "apollo-link-error": "^1.0.9",
7 "apollo-link": "^1.2.2"
8 }

Apollo client receives a link and cache properties. Setting up the cache will be much more straight-
forward than setting up the link. The Apollo client constructor receives an object with link and
cache properties. Import InMemoryCache from apollo-cache-inmemory, initialize InMemoryCache
and pass it to Apollo client.
5. Subscriptions 81

1 // ...
2 import { ApolloClient } from "apollo-client";
3 import { InMemoryCache } from "apollo-cache-inmemory";
4
5 // ...
6
7 const client = new ApolloClient({
8 cache: new InMemoryCache()
9 });

Now add a link property to new ApolloClient(). To create a single link from the previous links,
you will use ApolloLink.from. This function receives an array of links, merges them and returns a
single link.
You will pass three links to ApolloLink.from. The first will contain a function that runs every time
there is a network error. Create it using onError from apollo-link-error. The role of this function
will be to check if the error is a network or GraphQL error, and log it accordingly.

1 onError(({ graphQLErrors, networkError }) => {


2 if (graphQLErrors)
3 graphQLErrors.map(({ message, locations, path }) =>
4 console.log(
5 `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${p\
6 ath}`
7 )
8 );
9 if (networkError)
10 console.log(`[Network error]: ${networkError}`);
11 });

The second link will simulate Apollo Boost’s request interception. The app uses this feature to insert
the token in every request, so it’s important to keep providing this. The implementation of this
function uses Observables. You could think as Observables as a superset of Promises. Learning about
Observables is outside the scope of this book, but don’t worry, we will only use them in this snippet.
React Apollo has great information about how to migrate from Apollo Boost. They introduce this
implementation of request interceptor in their migration docs³⁵.
This is how you create an ApolloLink that intercepts every request:

³⁵https://www.apollographql.com/docs/react/advanced/boost-migration.html#advanced-migration
5. Subscriptions 82

1 new ApolloLink((operation, forward) => {


2 const request = async operation => {
3 if (this.state.token) {
4 operation.setContext({
5 headers: { Authorization: this.state.token }
6 });
7 }
8 };
9 return new Observable(observer => {
10 let handle;
11 Promise.resolve(operation)
12 .then(oper => request(oper))
13 .then(() => {
14 handle = forward(operation).subscribe({
15 next: observer.next.bind(observer),
16 error: observer.error.bind(observer),
17 complete: observer.complete.bind(observer)
18 });
19 })
20 .catch(observer.error.bind(observer));
21
22 return () => {
23 if (handle) handle.unsubscribe();
24 };
25 });
26 });

Luckily, to add the third link, HttpLink, you just need to create a new instance of it and point it to
your API’s URL.

1 new HttpLink({
2 uri: process.env.REACT_APP_API_URL,
3 credentials: "same-origin"
4 });

This is how the initialization of ApolloClient looks like after adding all links and cache:
5. Subscriptions 83

1 import React from "react";


2 import { Container, Nav } from "pinapp-components";
3 import { ApolloProvider } from "react-apollo";
4 import { ApolloClient } from "apollo-client";
5 import { InMemoryCache } from "apollo-cache-inmemory";
6 import { HttpLink } from "apollo-link-http";
7 import { onError } from "apollo-link-error";
8 import { ApolloLink, Observable } from "apollo-link";
9
10 // ...
11
12 const client = new ApolloClient({
13 link: ApolloLink.from([
14 // Simulate Apollo Boost error handling
15 onError(({ graphQLErrors, networkError }) => {
16 if (graphQLErrors)
17 graphQLErrors.map(({ message, locations, path }) =>
18 console.log(
19 `[GraphQL error]: Message: ${message}, Location: ${locations}, Path:\
20 ${path}`
21 )
22 );
23 if (networkError)
24 console.log(`[Network error]: ${networkError}`);
25 }),
26 // Enable dynamic request interceptors
27 new ApolloLink((operation, forward) => {
28 const request = async operation => {
29 if (this.state.token) {
30 operation.setContext({
31 headers: { Authorization: this.state.token }
32 });
33 }
34 };
35 return new Observable(observer => {
36 let handle;
37 Promise.resolve(operation)
38 .then(oper => request(oper))
39 .then(() => {
40 handle = forward(operation).subscribe({
41 next: observer.next.bind(observer),
42 error: observer.error.bind(observer),
5. Subscriptions 84

43 complete: observer.complete.bind(observer)
44 });
45 })
46 .catch(observer.error.bind(observer));
47
48 return () => {
49 if (handle) handle.unsubscribe();
50 };
51 });
52 }),
53 new HttpLink({
54 uri: process.env.REACT_APP_API_URL,
55 credentials: "same-origin"
56 })
57 ]),
58 cache: new InMemoryCache()
59 });

As in every important PinApp milestone, feel free to remix this step³⁶.


And that’s how you migrate from Apollo Boost. The ability to compose links provides the starting
point for adding subscriptions to Apollo Client. The next section will teach you how to add a
websockets transport using apollo-link-ws.

5.6 Implementing client side subscriptions


In this section you will add to PinListPage the ability to subscribe for more pins. You will achieve
this using GraphQL subscriptions. The app previously used refetchQueries to fetch all pins once
the user added a new one. This new method will be much more efficient, because you won’t send a
new query, but instead listen to new pins and add them individually.
The first step is adding to Apollo Client the ability to determine whether an operation needs to
be handled using HTTP or Websockets. To achieve this, you will use a function from apollo-
client called split³⁷. It receives three functions as argument. The first function determines whether
an operation should use the link from the second argument if it returns true, or the second link
otherwise.
Replace the third element of the ApolloLink.from array in src/App.js with a split call that
redirects subscriptions to WebSocketLink:

³⁶https://glitch.com/edit/#!/remix/pinapp-apollo-boost-migration
³⁷https://www.apollographql.com/docs/link/composition.html#directional
5. Subscriptions 85

1 import { ApolloLink, Observable, split } from "apollo-link";


2 import { WebSocketLink } from "apollo-link-ws";
3 import { getMainDefinition } from "apollo-utilities";
4
5 // ...
6
7 const client = new ApolloClient({
8 link: ApolloLink.from([
9 // ...,
10 // ...,
11 split(
12 // split based on operation type
13 ({ query }) => {
14 const { kind, operation } = getMainDefinition(
15 query
16 );
17 return (
18 kind === "OperationDefinition" &&
19 operation === "subscription"
20 );
21 },
22 new WebSocketLink({
23 uri: process.env.REACT_APP_API_URL.replace(
24 "https://",
25 "wss://"
26 ),
27 options: {
28 reconnect: true
29 }
30 }),
31 new HttpLink({
32 uri: process.env.REACT_APP_API_URL,
33 credentials: "same-origin"
34 })
35 )
36 ]),
37 cache: new InMemoryCache()
38 });

Now that your client knows how to handle subscriptions, add a pinAdded subscription to src/-
queries.js:
5. Subscriptions 86

1 export const PINS_SUBSCRIPTION = gql`


2 subscription {
3 pinAdded {
4 title
5 link
6 image
7 id
8 user_id
9 }
10 }
11 `;

Remove the refetchQueries prop in src/AddPinPage.js. The app is going to subscribe to new pins,
instead of fetching all pins when a new one is added.
The last step is adding a subscribeToMore function to the pin list component. It will call that function
when it mounts.

1 class PinListPageContainer extends React.Component {


2 componentDidMount() {
3 this.props.subscribeToMore();
4 }
5 render() {
6 return <PinListPage pins={this.props.pins} />;
7 }
8 }

You will create a new component that will send it the subscribeToMore function as a prop. It will
pass that information, along with the list of pins, using the functions as children pattern.

1 export default () => (


2 <PinListQuery>
3 {({ pins, subscribeToMore }) => (
4 <PinListPageContainer
5 pins={pins}
6 subscribeToMore={subscribeToMore}
7 />
8 )}
9 </PinListQuery>
10 );

The implementation of PinListQuery will be very similar to the implementation of PinListPage


from the previous version of PinApp. The main difference will be that it receives a function as
5. Subscriptions 87

children, and passes it a subscribeToMorePins function. It will create that function in its render
method, based on a property of the object that Query returns, called subscribeToMore.
subscribeToMore³⁸ uses the data it received on a query and merges it directly into the store.
It receives an object with two properties, a query in its document key, and a function called
updateQuery.

The function that determines how to merge the new data with the existing data is called update-
Query. It receives two arguments, the previous data stored as its first argument and the query
response on the second argument.
You are going to create a function that checks whether the new data contains a pinAdded property.
If it does, you will merge the pins property of the previous data with the new added pin.

1 class PinListQuery extends React.Component {


2 render() {
3 return (
4 <Query query={LIST_PINS}>
5 {({ loading, error, data, subscribeToMore }) => {
6 if (loading) {
7 return (
8 <Spinner
9 accessibilityLabel="Loading pins"
10 show
11 />
12 );
13 }
14 if (error) {
15 return <div>Error</div>;
16 }
17 const subscribeToMorePins = () => {
18 return subscribeToMore({
19 document: PINS_SUBSCRIPTION,
20 updateQuery: (prev, { subscriptionData }) => {
21 if (
22 !subscriptionData.data ||
23 !subscriptionData.data.pinAdded
24 ) {
25 return prev;
26 }
27 const newPinAdded =
28 subscriptionData.data.pinAdded;
29
³⁸https://www.apollographql.com/docs/react/advanced/subscriptions.html#subscribe-to-more
5. Subscriptions 88

30 return Object.assign({}, prev, {


31 pins: [...prev.pins, newPinAdded]
32 });
33 }
34 });
35 };
36 return this.props.children({
37 pins: data.pins,
38 subscribeToMore: subscribeToMorePins
39 });
40 }}
41 </Query>
42 );
43 }
44 }

That’s it! Now not only you will be able to see the pins you add in the list of pins, but anyone using
the app will see the created pin, thanks to subscriptions.
Remix this step’s project³⁹ if you got stuck in any step, and refer to the official React Apollo’s
subscriptions documentation⁴⁰ to learn more about subscriptions.

5.7 Summary
You learned what subscriptions are, how to add them both to the backend and frontend. Before
adding subscriptions to your backend, you migrated your database from SQLite3 to Postgres, because
it also serves as a PubSub system. You also migrated your frontend from Apollo Boost to Apollo
Client before adding subscriptions.
At this point, PinApp has several complex features, so adding a comprehensive test suite makes
sense. The next chapter will teach you how to test your backend and frontend using different testing
strategies.
³⁹https://glitch.com/edit/#!/remix/pinapp-client-subscriptions
⁴⁰https://www.apollographql.com/docs/react/advanced/subscriptions.html
6. Testing
Testing is key when producing solid software. A solid testing suite improves development speed
because it provides confidence that all features keep working after adding new functionality.
This chapter will teach you how to test GraphQL APIs and clients. You will write tests that verify
the behavior of all features you added in this book.
Let’s start by learning about API testing.

6.1 How to test GraphQL APIs


This section will teach you how to test GraphQL APIs using two approaches. The first will test the
GraphQL layer, and the second will test the HTTP layer. Both methods will use Jest⁴¹, a Javascript
testing library.
The first approach tests the GraphQL layer by sending queries and mutations directly against the
app’s schema.

Testing GraphQL layer

⁴¹http://facebook.github.io/jest/
6. Testing 90

The second approach tests that the HTTP layer works by creating a test client that sends queries
and mutations against a server.

Testing HTTP layer

Both methodologies have benefits. Testing the HTTP layer is a great way to verify that your API
works from the point of view of HTTP clients, which are the end users of an API. The other approach,
testing the GraphQL layer, is faster and simpler because it does not add any HTTP-related overhead.
Which one you choose depends on your use case. It is always a good idea to test systems from the
point of view of their users, so testing APIs in the HTTP layer is always a great approach. Sometimes
you want faster test runs to improve developer productivity, so you decide that testing the GraphQL
layer is the best approach. Remember that you can even mix and match approaches.

6.2 Testing setup


Before creating the tests itself, you will need to make some changes so that the codebase is more
testable. Right now server.js defines a Server class, initializes it and calls server.listen(). The
first change you need to make is split definition from usage.
6. Testing 91

Create a file called index.js. It will require the Server class from server.js, and call listen.

1 const Server = require("./server");


2
3 const server = new Server();
4
5 server.listen().then(({ url }) => {
6 console.log(`🚀 Server ready at ${url}`);
7 });

Modify the start script from package.json. It will run index.js instead of server.js.

1 "scripts": {
2 "start": "node index.js",
3 // ...
4 },

Now it’s time to prepare server.js for testing. You will modify Server so that you are able to setup
and stop it between tests. The Server class needs to initialize in its constructor all the resources it
needs, and free up those resources in its stop method.
Initialize database and pubsub in server’s constructor. At this point, database initialization happens
in database.js, and pubsub initialization happens in pins/resolver.js. Now they will both happen
in the Server constructor from server.js.
Also create a stop function in the Server class. It will clean up the main database client and the
pubsub database client. This is very important, because if you don’t clear up database connections
after each test run, you will have to manually stop your test suite because you will quickly run out
of available database connections.

1 const { ApolloServer } = require('apollo-server');


2 const { PostgresPubSub } = require("graphql-postgres-subscriptions");
3 const { Client } = require("pg");
4
5 const schema = require('./schema');
6 const createDatabase = require('./database');
7
8 class Server extends ApolloServer {
9 constructor() {
10 const database = createDatabase();
11 const client = new Client({
12 connectionString: process.env.NODE_ENV === "test" ?
13 `${process.env.TEST_DATABASE_URL}?ssl=true` :
6. Testing 92

14 `${process.env.DATABASE_URL}?ssl=true`
15 });
16 client.connect();
17 const pubsub = new PostgresPubSub({
18 client
19 });
20 super({
21 schema,
22 context: async ({ req }) => {
23 const context = { database, pubsub };
24 if (req && req.headers && req.headers.authorization) {
25 context.token = req.headers.authorization;
26 }
27 return context;
28 }
29 });
30 this.database = database;
31 this.pubsub = pubsub;
32 }
33 stop() {
34 return Promise.all([
35 super.stop(),
36 this.database.destroy(),
37 this.pubsub.client.end()
38 ]);
39 }
40 }
41
42 module.exports = Server;

You may have noticed a new database url, called TEST_DATABASE_URL. Create a new database in any
provider you’d like, and assign it to TEST_DATABASE_URL= in .env. Creating Postgres databases in
Heroku is free of charge.
Now all resolvers can access database from their third argument, context. Modify all three resolvers
by removing const database = require("../database") and accessing it from context.
Modify authentication/resolvers.js:
6. Testing 93

1 // ...
2 const resolvers = {
3 Query: {
4 users: async (_, __, { database }) => { /* */ },
5 me: async (_, __, { token, database }) => { /* */ }
6 },
7 Mutation: {
8 sendShortLivedToken: async (_, { email }, { database }) => { /* */ },
9 createLongLivedToken: (_, { token }) => { /* */ }
10 },
11 Person: { /* */ },
12 User: {
13 pins(person, _, { database }) { /* */ }
14 }

Modify pins/resolvers.js. Remove PostgresPubSub initialization, because it already is in server.js.


Access pubsub and database from resolvers’ context.

1 const { addPin } = require("./index");


2 const { verify, authorize } = require("../authentication");
3
4 const resolvers = {
5 Query: {
6 pins: (_ , __ , { database }) => database("pins").select(),
7 },
8 Mutation: {
9 addPin: async (_, { pin }, { token, database, pubsub }) => { /* */ }
10 },
11 Subscription: {
12 pinAdded: {
13 subscribe: (_, __, { pubsub }) => { /* */ }
14 }
15 }
16 };
17
18 module.exports = resolvers;

Modify search/resolvers.js:
6. Testing 94

1 const resolvers = {
2 Query: {
3 search: async (_, { text }, { database }) => { /* */ }
4 },
5 SearchResult: {
6 __resolveType: searchResult => { /* */ }
7 }
8 };
9
10 module.exports = resolvers;

Also modify database.js so that it exports an initialization function, instead of initializing the
database and exporting its instance.

1 module.exports = () => require('knex')(require("./knexfile"));

The final thing you need before you start writing tests is adding Jest to the "devDependencies" in
package.json and also adding a "test" script. This script will run jest --watchAll --runInBand.
watchAll reruns the test suite whenever a file changes, and runInBand runs all tests serially instead
of concurrently. This behavior is necessary because all tests share a single database, and running all
of them at the same time would result in data corruption.

1 {
2 "scripts": {
3 // ...
4 "test": "jest --watchAll --runInBand"
5 },
6 "devDependencies": {
7 "jest": "^22.4.3"
8 },

As with all examples, you can remix the testing example⁴² in case you need to refer to a working
project.

6.3 GraphQL layer


Testing the data layer is as simple as using the graphql function from graphql-js against your
schema. You will recognize this pattern, because it is the same approach you used to learn queries
⁴²https://glitch.com/edit/#!/remix/pinapp-server-testing
6. Testing 95

and mutations in Chapter 1. The only difference this time is that you will use this library in the
context of a Jest test.
To test queries using this approach, a good strategy is seeding the database before the first test,
and cleaning it up after the last one. This allows you to write fast tests that verify multiple queries,
because queries don’t modify your data.
Jest snapshots are a great tool to test results of GraphQL queries. Snapshots store values in JSON
files from each test on the first run. On successive runs of the test suite, Jest checks that the stored
values have not changed. If the snapshots changed, the test fails; otherwise, it passes.
Testing GraphQL results using snapshots is great because it is low effort way to verify that
everything works. You can write tests by focusing on requests, and not on responses. Focusing on
JSON responses can be a lot of manual work, so delegating it to Jest makes you write tests in less
time.
For example to write a test that checks the behavior of the search query, you could create a test that
calls graphql() with a search query, a "text" variable with value "First", and the app’s schema.
You are going to use this technique to test the data layer of PinApp’s. Create a file called
server.test.js with the following code that tests users, pins and search queries:

1 const { graphql } = require("graphql");


2
3 const createDatabase = require("./database");
4 const schema = require('./schema');
5 const { search } = require("./queries");
6
7 describe("GraphQL layer", () => {
8 let database;
9 beforeAll(async () => {
10 database = createDatabase();
11 return database.seed.run();
12 });
13 afterAll(() => database.destroy());
14
15 it("should return users' pins", () => {
16 const query = `
17 {
18 users {
19 id
20 email
21 pins {
22 user_id
23 }
6. Testing 96

24 }
25 }
26 `;
27 return graphql(schema, query, undefined, { database })
28 .then(result => {
29 expect(result.data.users).toMatchSnapshot();
30 });
31 });
32
33 it("should list all pins", () => {
34 const query = `
35 {
36 pins {
37 id
38 title
39 link
40 image
41 user_id
42 }
43 }
44 `;
45 return graphql(schema, query, undefined, { database })
46 .then(result => {
47 expect(result.data.pins).toMatchSnapshot();
48 });
49 });
50
51 it("should search pins by title", () => {
52 return graphql(schema, search, undefined, { database }, { text: "First" })
53 .then(result => {
54 expect(result.data.search).toMatchSnapshot();
55 });
56 });
57 });

This approach is inspired by an awesome open source project called Spectrum⁴³. It has an extensive
testing suite that uses Jest snapshots to test their GraphQL schema. Check out Spectrum’s github
repository⁴⁴ to see this approach in a production codebase.
Sometimes it’s best to recreate the exact conditions in which users interact with a system. In this
case, users are HTTP clients, not graphql-js clients. The next section will teach you how to test the
⁴³https://spectrum.chat/
⁴⁴https://github.com/withspectrum/spectrum/tree/e603e77bbb965bbbc7c678d9e9295e976c9381e0/api/test
6. Testing 97

HTTP layer of GraphQL APIs.

6.4 HTTP Layer


To test the HTTP layer, you are going to create an instance of Server before each test, and stop it
after each one. You are also going to delete all pins and users before each test, and delete all emails.

1 const { graphql } = require("graphql");


2
3 const createDatabase = require("./database");
4 const schema = require('./schema');
5 const { search } = require("./queries");
6 const Server = require("./server");
7 const { deleteEmails } = require("./email");
8
9 describe("GraphQL layer", () => { /* */ });
10
11 describe("HTTP layer", () => {
12 let server;
13 let serverInfo;
14
15 beforeEach(async () => {
16 server = new Server();
17 /*
18 Ignore event emitter errors.
19 In most cases this error appears because a database query got sent after c\
20 losing database connection.
21 */
22 server.pubsub.ee.on("error", () => {});
23 await Promise.all([
24 server.database("users").del(),
25 server.database("pins").del()
26 ]);
27 serverInfo = await server.listen({ http: { port: 3001 } });
28 deleteEmails();
29 });
30
31 afterEach(() => server.stop());
32
33 // Tests
34
35 });
6. Testing 98

Most of the time, the tests you can write against the HTTP layer are very similar to the tests you
can write agains the GraphQL layer. For example, testing that unauthorized users cannot add pins
consists of creating a query, and sending it either against an HTTP server or agains the schema
directly. In this case, we are going to write it against the HTTP server, but it is a matter of choice.

1 const { graphql } = require("graphql");


2 const fetch = require("isomorphic-unfetch");
3
4 // ... Previous imports
5 const Server = require("./server");
6 const { deleteEmails } = require("./email");
7
8 describe("GraphQL layer", () => { /* */ });
9
10 describe("HTTP layer", () => {
11 let server;
12 let serverInfo;
13
14 beforeEach(async () => { /* */ });
15
16 afterEach(() => server.stop());
17
18 it("should not allow unauthorized users to add pins", () => {
19 const variables = {
20 pin: {
21 title: "Example",
22 link: "http://example.com",
23 image: "http://example.com"
24 }
25 };
26 return fetch(serverInfo.url, {
27 body: JSON.stringify({ query: addPin, variables }),
28 headers: { "Content-Type": "application/json" },
29 method: "POST"
30 })
31 .then(response => response.json())
32 .then(response => {
33 expect(response.errors).toMatchSnapshot();
34 });
35 });
6. Testing 99

6.5 Testing email based authentication


Up until this point, you have been using an SMTP server like Ethereal⁴⁵. But there is a better option
for tests, Nodemailer provides the option of creating a JSON transport. This transporter does not
communicate with any other server, it just stores the list of mails as JSON objects.
Modify email.js by setting JSON transport in tests:

1 const nodemailer = require('nodemailer');


2
3 let transporter;
4
5 if (process.env.NODE_ENV === "test") {
6 transporter = nodemailer.createTransport({
7 jsonTransport: true
8 });
9 } else {
10 transporter = nodemailer.createTransport({
11 host: 'smtp.ethereal.email',
12 port: 587,
13 auth: {
14 user: process.env.MAIL_USER,
15 pass: process.env.MAIL_PASSWORD
16 }
17 });
18 }
19
20 function sendMail({ from, to, subject, text, html }) {
21 const mailOptions = {
22 from,
23 to,
24 subject,
25 text,
26 html
27 };
28 return new Promise((resolve, reject) => {
29 transporter.sendMail(mailOptions, (error, info) => {
30 if (error) {
31 return reject(error);
32 }
33 resolve(info);

⁴⁵https://ethereal.email
6. Testing 100

34 });
35 });
36 }
37
38 module.exports = {
39 sendMail
40 };

In order to test email authentication, you are going to need to access the list of emails sent. You can
keep an array of emails sent in email.js and expose them. You are also going to need a way to clean
up this list of emails, so you are also going to expose a function called deleteEmails.

1 const nodemailer = require('nodemailer');


2
3 let transporter;
4 var emails = [];
5
6 if (process.env.NODE_ENV === "test") {
7 /* */
8 } else {
9 /* */
10 }
11
12 function sendMail({ from, to, subject, text, html }) {
13 const mailOptions = { /* */ };
14 emails.push(mailOptions);
15 return new Promise((resolve, reject) => { /* */ });
16 }
17
18 function deleteEmails() {
19 while(emails.length > 0) {
20 emails.pop();
21 }
22 }
23
24 module.exports = {
25 emails,
26 sendMail,
27 deleteEmails
28 };

To test that users can create short lived tokens, you can send a createShortLivedToken query agains
the server, and check that it sent an email containing the user’s address.
6. Testing 101

1 const { graphql } = require("graphql");


2 const fetch = require("isomorphic-unfetch");
3
4 // ... Previous imports
5 const {
6 search,
7 createShortLivedToken,
8 } = require("./queries");
9 const Server = require("./server");
10 const { deleteEmails, emails } = require("./email");
11
12 describe("GraphQL layer", () => { /* */ });
13
14 describe("HTTP layer", () => {
15 let server;
16 let serverInfo;
17
18 beforeEach(async () => { /* */ });
19
20 afterEach(() => server.stop());
21
22 // ...
23
24 it("should allow users to create short lived tokens", () => {
25 const email = "name@example.com";
26 const variables = {
27 email
28 };
29 return fetch(serverInfo.url, {
30 body: JSON.stringify({ query: createShortLivedToken, variables }),
31 headers: { "Content-Type": "application/json" },
32 method: "POST"
33 })
34 .then(response => response.json())
35 .then(response => {
36 expect(emails[emails.length - 1].to).toEqual(email)
37 });
38 });
39 });

Testing that users can create long lived token is a little more complex. The strategy for testing this
would be to first create a short lived token, then parse the token from the email sent and send it to
the server as a "token" variable, along with a createLongLivedToken query.
6. Testing 102

To parse the token, you are going to use Node API’s url.parse⁴⁶ function. When you pass it a URL
as a first argument, and true as the second, it returns a query object. Parsing the url sent in the
email message will contain a token key.
To verify that the long lived token generated with createLongLivedToken is valid, you are going to
use the verify function from authenticate/index.js. It returns the token data, or an error if the
token is not valid. Checking that the token’s email is the same as the user’s email will be enough to
verify that authentication works.

1 const { graphql } = require("graphql");


2 const fetch = require("isomorphic-unfetch");
3 const url = require("url");
4
5 // ... Previous imports
6 const {
7 search,
8 createShortLivedToken,
9 createLongLivedToken,
10 } = require("./queries");
11 const Server = require("./server");
12 const { deleteEmails, emails } = require("./email");
13 const { verify } = require("./authentication");
14
15 describe("GraphQL layer", () => { /* */ });
16
17 describe("HTTP layer", () => {
18 let server;
19 let serverInfo;
20
21 beforeEach(async () => { /* */ });
22
23 afterEach(() => server.stop());
24
25 // ...
26
27 it("should allow users to create long lived tokens", () => {
28 const email = "name@example.com";
29 const variables = {
30 email
31 };
32 return fetch(serverInfo.url, {
33 body: JSON.stringify({ query: createShortLivedToken, variables }),
⁴⁶https://nodejs.org/docs/latest/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost
6. Testing 103

34 headers: { "Content-Type": "application/json" },


35 method: "POST"
36 })
37 .then(response => response.json())
38 .then(response => {
39 const token = url.parse(emails[emails.length - 1].text, true).query.token;
40 return fetch(serverInfo.url, {
41 body: JSON.stringify({ query: createLongLivedToken, variables: { token }\
42 }),
43 headers: { "Content-Type": "application/json" },
44 method: "POST"
45 })
46 })
47 .then(response => response.json())
48 .then(response => {
49 expect(verify(response.data.createLongLivedToken).email).toEqual(email);
50 });
51 });
52 });

Testing that the app returns the current authenticated user consists of checking that the me query
works. In order to test this, you need to simulate a login flow by creating a short lived token and
exchanging it with a long lived one, finally passing it to the me query.

1 it("should return authenticated user", () => {


2 const email = "name@example.com";
3 const variables = {
4 email
5 };
6 let token;
7 return fetch(serverInfo.url, {
8 body: JSON.stringify({ query: createShortLivedToken, variables }),
9 headers: { "Content-Type": "application/json" },
10 method: "POST"
11 })
12 .then(response => response.json())
13 .then(response => {
14 token = url.parse(emails[emails.length - 1].text, true).query.token;
15 return fetch(serverInfo.url, {
16 body: JSON.stringify({ query: createLongLivedToken, variables: { token } }\
17 ),
18 headers: { "Content-Type": "application/json" },
6. Testing 104

19 method: "POST"
20 })
21 })
22 .then(response => response.json())
23 .then(response => {
24 return fetch(serverInfo.url, {
25 body: JSON.stringify({ query: me }),
26 headers: { "Content-Type": "application/json", Authorization: token },
27 method: "POST"
28 });
29 })
30 .then(response => response.json())
31 .then(response => {
32 expect(response.data).toMatchSnapshot();
33 });
34 });

Another test that needs a complete login flow is checking that authenticated users can create pins.
To test this, complete a login flow and send a long lived token, along with the addPin query to the
server.

1 it("should allow authenticated users to create pins", () => {


2 const email = "name@example.com";
3 const variables = {
4 email
5 };
6 let token;
7 return fetch(serverInfo.url, {
8 body: JSON.stringify({ query: createShortLivedToken, variables }),
9 headers: { "Content-Type": "application/json" },
10 method: "POST"
11 })
12 .then(response => response.json())
13 .then(response => {
14 token = url.parse(emails[emails.length - 1].text, true).query.token;
15 return fetch(serverInfo.url, {
16 body: JSON.stringify({ query: createLongLivedToken, variables: { token }\
17 }),
18 headers: { "Content-Type": "application/json" },
19 method: "POST"
20 })
21 })
6. Testing 105

22 .then(response => response.json())


23 .then(response => {
24 const pin = {
25 title: "Example",
26 link: "http://example.com",
27 image: "http://example.com"
28 };
29 return fetch(serverInfo.url, {
30 body: JSON.stringify({ query: addPin, variables: { pin } }),
31 headers: { "Content-Type": "application/json", Authorization: token },
32 method: "POST"
33 });
34 })
35 .then(response => response.json())
36 .then(response => {
37 expect(response.data).toMatchSnapshot();
38 });
39 });

This test completes all authentication related tests. The following section will teach you how to
verify that subscriptions work in your API.

6.6 Subscription endpoints


To test GraphQL Subscriptions you need a Websockets client, in the same way that you need
an HTTP client to test queries and mutations. In this section you are going to use a Websockets
subscriptions client from the "subscriptions-transport-ws" library⁴⁷.
The first step is adding this library to package.json’s "dependencies".

1 {
2 "dependencies": {
3 // ...
4 "subscriptions-transport-ws": "^0.9.9"
5 }
6 }

Testing a subscription query (like pinAdded from PinApp schema) involves pointing an instance of
SubscriptionClient to a subscriptions url, sending the query and checking that the result is valid.
To test pinAdded you need to simulate a login flow and create a pin. You are going to put this logic
in a helper function called authenticateAndAddPin. It contains almost the same steps as the add pin
test.
⁴⁷https://github.com/apollographql/subscriptions-transport-ws
6. Testing 106

1 const { graphql } = require("graphql");


2 const fetch = require("isomorphic-unfetch");
3 const url = require("url");
4 const { SubscriptionClient } = require("subscriptions-transport-ws");
5
6 // ...
7
8 describe("HTTP layer", () => {
9 // ...
10 it("should subscribe to pins", done => {
11 const subscriptionClient = new SubscriptionClient(
12 serverInfo.url.replace("http://", "ws://"),
13 {
14 reconnect: true,
15 connectionCallback: error => {
16 if (error) {
17 done(error);
18 }
19 }
20 }
21 );
22 subscriptionClient.on("connected", () => {
23 subscriptionClient
24 .request({
25 query: pinsSubscription
26 })
27 .subscribe({
28 next: result => {
29 expect(result).toMatchSnapshot();
30 done();
31 },
32 error: done
33 });
34 authenticateAndAddPin(serverInfo.url);
35 });
36 subscriptionClient.on("error", done);
37 });
38 });
39
40 function authenticateAndAddPin(serverUrl) {
41 const email = "name@example.com";
42 const variables = {
6. Testing 107

43 email
44 };
45 let token;
46 return fetch(serverUrl, {
47 body: JSON.stringify({ query: createShortLivedToken, variables }),
48 headers: { "Content-Type": "application/json" },
49 method: "POST"
50 })
51 .then(response => {
52 token = url.parse(emails[emails.length - 1].text, true).query.token;
53 const pin = {
54 title: "Example",
55 link: "http://example.com",
56 image: "http://example.com"
57 };
58 return fetch(serverUrl, {
59 body: JSON.stringify({ query: addPin, variables: { pin } }),
60 headers: { "Content-Type": "application/json", Authorization: token },
61 method: "POST"
62 })
63 .then(response => response.json());
64 })
65 .then(response => {
66 if (response.errors) {
67 throw new Error(response.errors[0].message);
68 }
69 })
70 }

This is the final step in testing PinApp’s API. The next sections will teach you how to test GraphQL
clients, more specifically how to test Apollo GraphQL clients.

6.7 How to test React Apollo GraphQL clients


In this chapter you will learn how to test React Apollo clients. To do this, you will use Jest⁴⁸ as a test
runner, Enzyme⁴⁹ because it provides testing tools for React, and React Apollo’s testing utilities.
To test the network layers, you are going to take advantage of the fact that Apollo GraphQL’s
network layer is configurable using Apollo Link⁵⁰. The strategy is swapping the Provider defined in
⁴⁸https://facebook.github.io/jest/
⁴⁹https://github.com/airbnb/enzyme/
⁵⁰https://www.apollographql.com/docs/react/advanced/network-layer.html
6. Testing 108

src/App.js with a MockedProvider. This Provider is useful for testing purposes because it does not
communicate with any server, instead it receives an array of mocks that it uses for sending GraphQL
responses. If MockedProvider has a mock that corresponds to a request, it sends the mock’s response.
If no mock matches a request, it throws an error.
As with all steps, you have the chance to remix the current example⁵¹ in case you need any help.
Let’s write a basic test. You may have seen this test a bunch of times if you are used to bootstrapping
apps using create-react-app⁵². This test verifies that the app renders without crashing. To stop the
app from making network requests, you will use Jest to replace ApolloProvider with a dummy
component. You will also wrap the app with React Router’s MemoryRouter, because Jest runs in
Node, not in the browser.
Create a file called src/App.test.js with the following contents:

1 import React from "react";


2 import ReactDOM from "react-dom";
3 import { MockedProvider } from "react-apollo/test-utils";
4 import * as ReactRouter from "react-router";
5 import * as ReactApollo from "react-apollo";
6
7 const MemoryRouter = ReactRouter.MemoryRouter;
8
9 ReactApollo.ApolloProvider = jest.fn(({ children }) => <div>{children}</div>);
10
11 import App from "./App";
12
13 it("renders without crashing", () => {
14 const div = document.createElement("div");
15 ReactDOM.render(
16 <MemoryRouter>
17 <MockedProvider mocks={[]}>
18 <App />
19 </MockedProvider>
20 </MemoryRouter>,
21 div
22 );
23 ReactDOM.unmountComponentAtNode(div);
24 });

You also need to pass a property called noRouter to pinapp-component’s Container. Otherwise
it will try to use a Router implementation which depends on the browser’s history API, which
⁵¹https://glitch.com/edit/#!/remix/pinapp-client-testing
⁵²https://github.com/facebook/create-react-app
6. Testing 109

is not available in Node. Pass noRouter={process.env.NODE_ENV === "test"} to Container in


src/App.js

1 // ...
2 export default class App extends React.Component {
3 // ...
4 render() {
5 return (
6 <ApolloProvider client={this.state.client}>
7 <Container noRouter={process.env.NODE_ENV === "test"}>
8 {/* */}
9 </Container>
10 </ApolloProvider>
11 );
12 }

Finally install react-router by adding it to package.json. Note that the previous test will work
whether or not you install react-router. This happens because pinapp-components already has
React Router as a dependency. But now React Router is also a dependency of your app, because you
use MemoryRouter in your tests.
You also need to install jest-cli if you are following the examples on glitch. This is a temporary
workaround because of a bug in pnpm, which is the package manager that Glitch uses. It is similar to
NPM or Yarn, but much more disk efficient because it uses symlinks instead of installing duplicated
packages. You normally don’t need to install Jest if you are using react-scripts with Yarn or NPM,
so skip jest-cli if you are developing outside of Glitch.

1 {
2 "dependencies": {
3 // ...
4 "jest-cli": "23.0.1",
5 "react-router": "^4.2.0"
6 }
7 }

Run the test suite by opening the console and running npm test.
Now let’s write a test based on a use case of the app. You are going to verify that the app shows the
text “There are no pins yet” initially.
Instead of using React to render the App, you will use Enzyme’s mount function. It performs a full
DOM rendering. just like calling ReactDOM.render, the difference is that choosing mount allows you
to use Enzyme’s querying and expectations capabilities.
6. Testing 110

You will pass a mock list instead of an empty array to MockedProvider. Mocks are object with two
keys, request and result. request is an object that has a query key and can have a variables
key. result contains a Javascript object that simulates the server’s response. In this case mocks will
consist of two elements, the first simulates a LIST_PINS query with a list of empty pins as response,
and the second simulates a PINS_SUBSCRIPTION query with no pin as a response. These are the two
requests that App sends when it starts.

1 // ...
2 import {
3 LIST_PINS,
4 PINS_SUBSCRIPTION,
5 CREATE_SHORT_LIVED_TOKEN,
6 CREATE_LONG_LIVED_TOKEN,
7 ME,
8 ADD_PIN
9 } from "./queries";
10
11 it("shows 'There are no pins yet' initially", async () => {
12 const mocks = [
13 {
14 request: { query: LIST_PINS },
15 result: {
16 data: {
17 pins: []
18 }
19 }
20 },
21 {
22 request: {
23 query: PINS_SUBSCRIPTION
24 },
25 result: { data: {Â pinAdded: null } }
26 }
27 ];
28 const wrapper = mount(
29 <MemoryRouter>
30 <MockedProvider mocks={mocks}>
31 <App />
32 </MockedProvider>
33 </MemoryRouter>
34 );
35 // Wait for async pins query
36 await wait();
6. Testing 111

37 // Manually update enzyme wrapper


38 // https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-t\
39 o-3.md#for-mount-updates-are-sometimes-required-when-they-werent-before)
40 wrapper.update();
41 expect(
42 wrapper.contains(node => node.text() === "There are no pins yet.")
43 ).toBe(true);
44 wrapper.unmount();
45 });

Another useful test would be verifying that the app shows a list of pins when it receives a non empty
pins response. The test structure for doing this is very similar to the previous test, with the difference
that the LIST_PINS query will contain a list of pins in the response. This test will verify that there is
an element with class pins that has three elements with class pin.

1 it("should show a list of pins", async () => {


2 const pins = [
3 {
4 id: "1",
5 title: "Modern",
6 link: "https://pinterest.com/pin/637540890973869441/",
7 image:
8 "https://i.pinimg.com/564x/5a/22/2c/5a222c93833379f00777671442df7cd2.jpg"
9 },
10 {
11 id: "2",
12 title: "Broadcast Clean Titles",
13 link: "https://pinterest.com/pin/487585097141051238/",
14 image:
15 "https://i.pinimg.com/564x/85/ce/28/85ce286cba63daf522464a7d680795ba.jpg"
16 },
17 {
18 id: "3",
19 title: "Drawing",
20 link: "https://pinterest.com/pin/618611698790230574/",
21 image:
22 "https://i.pinimg.com/564x/00/7a/2e/007a2ededa8b0ce87e048c60fa6f847b.jpg"
23 }
24 ];
25 const mocks = [
26 {
27 request: { query: LIST_PINS },
6. Testing 112

28 result: {
29 data: {
30 pins
31 }
32 }
33 },
34 {
35 request: {
36 query: PINS_SUBSCRIPTION
37 },
38 result: { data: {Â pinAdded: null } }
39 }
40 ];
41 const wrapper = mount(
42 <MemoryRouter>
43 <MockedProvider mocks={mocks}>
44 <App />
45 </MockedProvider>
46 </MemoryRouter>
47 );
48 await wait();
49 wrapper.update();
50 expect(wrapper.find(".pins .pin").length).toBe(3);
51 wrapper.unmount();
52 });

6.8 Testing client-side authentication


The login flow consists of two steps. The first is when the user clicks login, and then fill the email
input with an email address, clicking submit afterwards. The second step happens when the user
clicks the link in the received email, going to /verify?token=123456, which will authenticate the
user if the token is valid.
To test the first step, let’s write a test that simulates the action that the user needs to take in order
to receive a magic link in its email address. The first action is clicking the login button in the app’s
footer, which will redirect the user to the login page.

1 wrapper.find('a[href="/login"]').simulate("click", { button: 0 });

To simulate user’s actions, you will use an Enzyme function called prop. This function allows you
to access properties from React components. In this case, it will be useful to access the onChange
function from the email input, and the onSubmit function from the email form.
6. Testing 113

The app will need a mock that will handle the API call when the user sends a CREATE_SHORT_-
LIVED_TOKEN mutation, so you will add this mock to the list. If you don’t add this mock, the test will
fail.
Finally this test will verify that the app shows a an “Email sent” message.

1 it("should allow users to login", async () => {


2 const email = "name@example.com";
3 const mocks = [
4 {
5 request: { query: LIST_PINS },
6 result: {
7 data: {
8 pins: []
9 }
10 }
11 },
12 {
13 request: {
14 query: PINS_SUBSCRIPTION
15 },
16 result: { data: {Â pinAdded: null } }
17 },
18 {
19 request: {
20 query: CREATE_SHORT_LIVED_TOKEN,
21 variables: {
22 email
23 }
24 },
25 result: {
26 data: {
27 sendShortLivedToken: true
28 }
29 }
30 }
31 ];
32 const wrapper = mount(
33 <MemoryRouter>
34 <MockedProvider mocks={mocks}>
35 <App />
36 </MockedProvider>
37 </MemoryRouter>
6. Testing 114

38 );
39 await wait();
40 wrapper.update();
41 expect(wrapper.find(".auth-banner").length).toBe(1);
42 expect(wrapper.find('a[href="/profile"]').length).toBe(0);
43 wrapper.find('a[href="/login"]').simulate("click", { button: 0 }); // Add { bu\
44 tton: 0 } because of React Router bug https://github.com/airbnb/enzyme/issues/51\
45 6
46 wrapper
47 .find("#email")
48 .first()
49 .prop("onChange")({ value: email });
50 await wait();
51 wrapper.update();
52 wrapper.find("form").prop("onSubmit")({ preventDefault: () => {} });
53 await wait();
54 wrapper.update();
55 expect(
56 wrapper.contains(
57 node =>
58 node.text() === `We sent an email to ${email}. Please check your inbox.`
59 )
60 ).toBe(true);
61 wrapper.unmount();
62 });

To test that the app authenticates users who enter the verify page, you will use a property from
MemoryRouter called initialEntries. This property receives an array of URLs, so passing it
['/verify?token=${token}'] will start the app on the Verify page.

The list of mocks will need a response for the CREATE_LONG_LIVED_TOKEN query, containing a string
that represents the auth token.
To verify that the authentication works, you will simulate a user who enters to the Profile page after
a successful authentication. This is why you will add a response to the ME query to the list of mocks.
Checking that the app shows the user’s email is enough to verify that this test works.
6. Testing 115

1 it("should authenticate users who enter verify page", async () => {


2 const email = "name@example.com";
3 const token = "5minutes";
4 const mocks = [
5 {
6 request: { query: LIST_PINS },
7 result: {
8 data: {
9 pins: []
10 }
11 }
12 },
13 {
14 request: {
15 query: PINS_SUBSCRIPTION
16 },
17 result: { data: {Â pinAdded: null } }
18 },
19 {
20 request: {
21 query: CREATE_LONG_LIVED_TOKEN,
22 variables: {
23 token
24 }
25 },
26 result: {
27 data: {
28 createLongLivedToken: "30days"
29 }
30 }
31 },
32 {
33 request: { query: ME },
34 result: {
35 data: {
36 me: { email }
37 }
38 }
39 }
40 ];
41 const initialEntries = [`/verify?token=${token}`];
42 const wrapper = mount(
6. Testing 116

43 <MemoryRouter initialEntries={initialEntries}>
44 <MockedProvider mocks={mocks}>
45 <App />
46 </MockedProvider>
47 </MemoryRouter>
48 );
49 await wait();
50 wrapper.update();
51 // Verify Page shows "Success!" for 1 second (1000 ms), then redirects to "/"
52 await wait(1000);
53 wrapper.update();
54 wrapper.find('a[href="/profile"]').simulate("click", { button: 0 });
55 await wait();
56 wrapper.update();
57 expect(
58 wrapper.find(".profile-page").contains(node => node.text() === email)
59 ).toBe(true);
60 wrapper.unmount();
61 });

In the next step you will learn how to test client side subscriptions by creating a test that verifies
that users can add pins.

6.9 Client subscriptions


MockedProvider is perfect for mocking request/response pairs, but it does not provide a way of
testing server sent events, like subscriptions. Fortunately, React Apollo provides the tools you need
to mock server sent events with MockSubscriptionLink.
To simulate subscription results, you can create an instance of MockSubscriptionLink and use a
function called simulateResult.

1 subscriptionsLink.simulateResult({
2 result: {
3 data: {
4 pinAdded: {
5 title,
6 link,
7 image,
8 id: "1"
9 }
10 }
6. Testing 117

11 }
12 });

The strategy for testing subscriptions will be creating a custom MockContainer, and accessing
subscriptionsLink by exposing it as a class property. This allows you to call simulateResult
anywhere in the test.
This MockContainer will have the same API and implementation as React Apollo’s MockProvider.
It will receive a list of mocks and create a MockLink using this list. It will merge this link with
an instance of MockSubscriptionLink using split. To determine which link MockSubscription-
sProvider uses, you are going to define the same logic that you used to decide between HttpLink
and WebsocketLink in src/App.js.
Import the new dependencies and define a class called MockSubscriptionLink at the end of
src/App.test.js.

1 // ...
2 import {
3 MockedProvider,
4 MockLink,
5 MockSubscriptionLink
6 } from "react-apollo/test-utils";
7 import { InMemoryCache as Cache } from "apollo-cache-inmemory";
8 import { getMainDefinition } from "apollo-utilities";
9 import { split } from "apollo-link";
10 import ApolloClient from "apollo-client";
11
12 const ApolloProvider = ReactApollo.ApolloProvider;
13 const MemoryRouter = ReactRouter.MemoryRouter;
14
15 ReactRouter.Router = jest.fn(({ children }) => <div>{children}</div>);
16 ReactApollo.ApolloProvider = jest.fn(({ children }) => <div>{children}</div>);
17
18 // ...
19
20 it("should allow logged in users to add pins", async () => { /* */ });
21
22 class MockedSubscriptionsProvider extends React.Component {
23 constructor(props, context) {
24 super(props, context);
25 const subscriptionsLink = new MockSubscriptionLink();
26 const addTypename = false;
27 const mocksLink = new MockLink(props.mocks, addTypename);
6. Testing 118

28 const link = split(


29 // split based on operation type
30 ({ query }) => {
31 const { kind, operation } = getMainDefinition(query);
32 return kind === "OperationDefinition" && operation === "subscription";
33 },
34 subscriptionsLink,
35 mocksLink
36 );
37 const client = new ApolloClient({
38 link,
39 cache: new Cache({ addTypename })
40 });
41 this.client = client;
42 this.subscriptionsLink = subscriptionsLink;
43 }
44 render() {
45 return (
46 <ApolloProvider client={this.client}>
47 {this.props.children}
48 </ApolloProvider>
49 );
50 }
51 }

Now it’s time to verify that logged in users can create pins, and the new pins appear in the list. This
test will perform the same initial steps as the previous authentication tests. It will differ with those
tests once it authenticates a user, because it will navigate to the add pin page instead of the profile.
Once the user is in the add pin page, it will simulate the user filling out the new pin form and clicking
“Add”. For this to complete successfully. you will add a mock for the ADD_PIN query to the mocks
list.
After this, the test will simulate a new subscription result by accessing the subscriptionsLink from
the MockedSubscriptionsProvider instance and calling simulateResult with a new pin.
The test will check that this new pin appears in the pins list by using expect(wrapper.find(".pins
.pin").length).toBe(1);.
6. Testing 119

1 it("should allow logged in users to add pins", async () => {


2 const title = "GraphQL College";
3 const link = "http://graphql.college";
4 const image = "http://www.graphql.college/fullstack-graphql";
5 const email = "name@example.com";
6 const token = "5minutes";
7 const mocks = [
8 {
9 request: { query: LIST_PINS },
10 result: {
11 data: {
12 pins: []
13 }
14 }
15 },
16 {
17 request: {
18 query: PINS_SUBSCRIPTION
19 },
20 result: { data: {Â pinAdded: null } }
21 },
22 {
23 request: {
24 query: CREATE_LONG_LIVED_TOKEN,
25 variables: {
26 token
27 }
28 },
29 result: {
30 data: {
31 createLongLivedToken: "30days"
32 }
33 }
34 },
35 {
36 request: { query: ME },
37 result: {
38 data: {
39 me: { email }
40 }
41 }
42 },
6. Testing 120

43 {
44 request: {
45 query: ADD_PIN,
46 variables: {
47 pin: {
48 title,
49 link,
50 image
51 }
52 }
53 },
54 result: {
55 data: {
56 addPin: {
57 title,
58 link,
59 image
60 }
61 }
62 }
63 }
64 ];
65 const initialEntries = [`/verify?token=${token}`];
66 const wrapper = mount(
67 <MemoryRouter initialEntries={initialEntries}>
68 <MockedSubscriptionsProvider mocks={mocks}>
69 <App />
70 </MockedSubscriptionsProvider>
71 </MemoryRouter>
72 );
73 await wait();
74 wrapper.update();
75 await wait(1000);
76 wrapper.update();
77 wrapper
78 .find('a[href="/upload-pin"]')
79 .first()
80 .simulate("click", { button: 0 });
81 wrapper.update();
82 wrapper
83 .find('[placeholder="Title"]')
84 .first()
6. Testing 121

85 .prop("onChange")({ target: { value: title } });


86 wrapper
87 .find('[placeholder="URL"]')
88 .first()
89 .prop("onChange")({ target: { value: link } });
90 wrapper
91 .find('[placeholder="Image URL"]')
92 .first()
93 .prop("onChange")({ target: { value: image } });
94 wrapper.update();
95 wrapper.find("form").prop("onSubmit")({ preventDefault: () => {} });
96 const subscriptionsLink = wrapper.find(MockedSubscriptionsProvider).instance()
97 .subscriptionsLink;
98 subscriptionsLink.simulateResult({
99 result: {
100 data: {
101 pinAdded: {
102 title,
103 link,
104 image,
105 id: "1"
106 }
107 }
108 }
109 });
110 await wait(1000);
111 wrapper.update();
112 expect(wrapper.find(".pins .pin").length).toBe(1);
113 wrapper.unmount();
114 });

Testing subscriptions is very straightforward once you can simulate results using MockSubscrip-
tionLink.

6.9 Summary
In this chapter you learned how to test GraphQL APIs and React Apollo clients.
You used two different strategies to write API tests, once that tests the GraphQL layer and another
that tests the HTTP layer. To write expectations, you used Jest snapshots in some cases and manual
expectations in other occasions.
6. Testing 122

You tested queries and mutations in React Apollo clients using MockedProvider. You also learned
how to test subscriptions by using MockSubscriptionLink to simulate server sent events.
Now you are ready to apply this techniques to verify the correct behavior of your GraphQL
Applications.

You might also like