Fullstack Graphql
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
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
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.
called graphql-js, but there are also many other incarnations in other programming languages like
Ruby, Elixir and more.
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:
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.
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¹.
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
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
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 }
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.
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
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 }
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 $ 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.
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.
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 $ 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.
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 }
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 $ 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 }
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.
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.
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.
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:
Make your own copy of this example with the following button:
2. Data modeling 27
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.
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
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 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
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.
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¹¹
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
You can chain together Knex functions. For example you can filter results from a select() by
chaining its call with the where() function.
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.
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.
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 };
¹²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:
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
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
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.
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
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.
¹⁷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;
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).
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:
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
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".
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
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.
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
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:
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 }
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 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
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.
²⁶https://glitch.com/edit/#!/remix/pinapp-apollo-client
²⁷https://reactjs.org/docs/faq-functions.html
4. GraphQL clients 63
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
43 `;
Remove PinListPage from the list of imports from pinapp-components and import it from the file
you just created.
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.
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”.
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
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.
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
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
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
1 "dependencies": {
2 // ...
3 "pg": "^7.4.3"
4 }
²⁹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.
³⁰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.
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
When a new pin is created, you can fire an event to notify all users of this new pin.
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.
³⁴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 }
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 }
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.
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.
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
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
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 });
³⁶https://glitch.com/edit/#!/remix/pinapp-apollo-boost-migration
³⁷https://www.apollographql.com/docs/link/composition.html#directional
5. Subscriptions 85
Now that your client knows how to handle subscriptions, add a pinAdded subscription to src/-
queries.js:
5. Subscriptions 86
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.
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.
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.
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.
⁴¹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.
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.
Create a file called index.js. It will require the Server class from server.js, and call listen.
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.
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 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.
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.
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:
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
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.
⁴⁵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.
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
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.
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.
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.
This test completes all authentication related tests. The following section will teach you how to
verify that subscriptions work in your API.
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
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.
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:
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
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
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.
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 });
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.
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
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.
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
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
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
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.