Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 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 Node.js 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 Node.js 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, Node.js, 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

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

To follow along you need Node.js and a code editor. Every chapter shows the code you need to write step by step, so you can build PinApp on your own machine and run each example from a terminal.

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.

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 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.

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

const schema = require("../schema");

const query = ``;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

Each example in this chapter is a script inside a queries folder, which requires the schema from schema.js. Run a script from a terminal to see its output, for example node queries/1-query.js.

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:

{
  users {
    email
  }
}

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

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

const schema = require("../schema");

const query = `
  {
    users {
      email
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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.

$ node queries/1-query.js
{
 "data": {
  "users": [
   {
    "email": "Hello World"
   },
   {
    "email": "Hello World"
   }
  ]
 }
}

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.

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.

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

const schema = require("../schema");

const query = `
  {
    users {
      email
      pins {
        title
      }
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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.

$ node queries/2-fields.js
{
 "data": {
  "users": [
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   },
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   }
  ]
 }
}

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.

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

const schema = require("../schema");

const query = `
  {
    users {
      email
    }
    pins {
      title
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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.

$ node queries/3-multiple-fields.js
{
 "data": {
  "users": [
   {
    "email": "Hello World"
   },
   {
    "email": "Hello World"
   }
  ],
  "pins": [
   {
    "title": "Hello World"
   },
   {
    "title": "Hello World"
   }
  ]
 }
}

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:

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

const schema = require("../schema");

const query = `
  query GetUsers {
    users {
      email
      pins {
        title
      }
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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.

$ node queries/4-operation-name.js
{
 "data": {
  "users": [
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   },
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   }
  ]
 }
}

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:

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

const schema = require("../schema");

const query = `
  query {
    pinById(id: "1") {
      title
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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

$ node queries/5-arguments.js
{
 "data": {
  "pinById": {
   "title": "Hello World"
  }
 }
}

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.

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.

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

const schema = require("../schema");

const query = `
  query {
    firstPin: pinById(id: "1") {
      title
    }
    secondPin: pinById(id: "2") {
      title
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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

$ node queries/6-aliases.js
{
 "data": {
  "firstPin": {
   "title": "Hello World"
  },
  "secondPin": {
   "title": "Hello World"
  }
 }
}

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 } syntax. 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.

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

const schema = require("../schema");

const query = `
  query {
    pins {
      ...pinFields
    }
    users {
      email
      pins {
        ...pinFields
      }
    }
  }
  fragment pinFields on Pin {
    title
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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.

$ node queries/7-fragments.js
{
 "data": {
  "pins": [
   {
    "title": "Hello World"
   },
   {
    "title": "Hello World"
   }
  ],
  "users": [
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   },
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   }
  ]
 }
}

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 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.

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

const schema = require("../schema");

const query = `
  query ($id: String!) {
    pinById(id: $id) {
      title
    }
  }
`;

graphql(schema, query, undefined, undefined, {
  id: "1",
}).then((result) => console.log(JSON.stringify(result, null, 1)));

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

$ node queries/8-variables.js
{
 "data": {
  "pinById": {
   "title": "Hello World"
  }
 }
}

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.

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.

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

const schema = require("../schema");

const query = `
  query ($withPins: Boolean!) {
    users {
      email
      pins @include(if: $withPins) {
        title
      }
    }
  }
`;

graphql(schema, query, undefined, undefined, {
  withPins: true,
}).then((result) => console.log(JSON.stringify(result, null, 1)));

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.

$ node queries/9-directives.js
{
 "data": {
  "users": [
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   },
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   }
  ]
 }
}

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.

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

const schema = require("../schema");

const query = `
  query ($withPins: Boolean = true) {
    users {
      email
      pins @include(if: $withPins) {
        title
      }
    }
  }
`;

graphql(schema, query).then((result) =>
  console.log(JSON.stringify(result, null, 1))
);

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

$ node queries/10-default-variables.js
{
 "data": {
  "users": [
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   },
   {
    "email": "Hello World",
    "pins": [
     {
      "title": "Hello World"
     },
     {
      "title": "Hello World"
     }
    ]
   }
  ]
 }
}

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.

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

const schema = require("../schema");

const query = `
  query ($text: String!) {
    search(text: $text) {
      ... on Person {
        email
      }
      ... on Pin {
        title
      }
    }
  }
`;

graphql(schema, query, undefined, undefined, {
  text: "Hello world",
}).then((result) => console.log(JSON.stringify(result, null, 1)));

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

$ node queries/11-inline-fragments.js
{
 "data": {
  "search": [
   {
    "title": "Hello World"
   },
   {
    "email": "Hello World"
   }
  ]
 }
}

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.

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

const schema = require("../schema");

const query = `
  query ($text: String!) {
    search(text: $text) {
      __typename
      ... on Person {
        email
      }
      ... on Pin {
        title
      }
    }
  }
`;

graphql(schema, query, undefined, undefined, {
  text: "Hello world",
}).then((result) => console.log(JSON.stringify(result, null, 1)));

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.

$ node queries/12-meta-fields.js
{
 "data": {
  "search": [
   {
    "__typename": "Admin",
    "email": "Hello World"
   },
   {
    "__typename": "Pin",
    "title": "Hello World"
   }
  ]
 }
}

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.

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.

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

const schema = require("../schema");

const query = `
  mutation AddPin($pin: PinInput!) {
    addPin(pin: $pin) {
      id
      title
      link
      image
    }
  }
`;

graphql(schema, query, undefined, undefined, {
  pin: {
    title: "Hello world",
    link: "Hello world",
    image: "Hello world",
  },
}).then((result) => console.log(JSON.stringify(result, null, 1)));

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.

$ node queries/13-mutations.js
{
 "data": {
  "addPin": {
   "id": "Hello World",
   "title": "Hello World",
   "link": "Hello World",
   "image": "Hello World"
  }
 }
}

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.

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

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 makeExecutableSchema.

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.

Create a file called schema.js. This is where you will define PinApp’s schema.

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

const typeDefs = importSchema("schema.graphql");
const resolvers = require("./resolvers");

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

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.

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:

type Pin {
  title: String!
  link: String!
  image: String!
  id: String!
  user_id: String!
}

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.

type Query {
  pins: [Pin]
  pinById(id: String!): Pin
  users: [User]
  me: User
  search(text: String): [SearchResult]
}

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:

type Mutation {
  addPin(pin: PinInput!): Pin
  sendShortLivedToken(email: String!): Boolean
  createLongLivedToken(token: String!): String
}

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:

enum PinStatus {
  DELETED
  HIDDEN
  VISIBLE
}

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:

input PinInput {
  title: String!
  link: String!
  image: String!
}

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.

interface Person {
  id: String!
  email: String!
  pins: [Pin]
}

type User implements Person {
  id: String!
  email: String!
  pins: [Pin]
}

type Admin implements Person {
  id: String!
  email: String!
  pins: [Pin]
}

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:

union SearchResult = User | Admin | Pin

type Query {
  # ...
  search(text: String): [SearchResult]
}

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

type Pin {
  title: String!
  link: String!
  image: String!
  id: String!
  user_id: String!
}

input PinInput {
  title: String!
  link: String!
  image: String!
}

interface Person {
  id: String!
  email: String!
  pins: [Pin]
}

type User implements Person {
  id: String!
  email: String!
  pins: [Pin]
}

type Admin implements Person {
  id: String!
  email: String!
  pins: [Pin]
}

union SearchResult = User | Admin | Pin

type Query {
  pins: [Pin]
  pinById(id: String!): Pin
  users: [User]
  me: User
  search(text: String): [SearchResult]
}

type Mutation {
  addPin(pin: PinInput!): Pin
  sendShortLivedToken(email: String!): Boolean
  createLongLivedToken(token: String!): String
}

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:

const {
  addPin,
  createShortLivedToken,
  sendShortLivedToken,
  createLongLivedToken,
  createUser,
} = require("./business-logic");

const database = {
  users: {},
  pins: {},
};

const resolvers = {
  Query: {
    pins: () => Object.values(database.pins),
    users: () => Object.values(database.users),
    search: (_, { text }) => {
      return [
        ...Object.values(database.pins).filter((pin) =>
          pin.title.includes(text)
        ),
        ...Object.values(database.users).filter((user) =>
          user.email.includes(text)
        ),
      ];
    },
  },
  Mutation: {
    addPin: async (_, { pin }, { user }) => {
      const { user: updatedUser, pin: createdPin } = await addPin(user, pin);
      database.pins[createdPin.id] = createdPin;
      database.users[user.id] = updatedUser;
      return createdPin;
    },
    sendShortLivedToken: (_, { email }) => {
      let user;
      const userExists = Object.values(database.users).find(
        (u) => u.email === user.email
      );
      if (userExists) {
        user = userExists;
      } else {
        user = createUser(email);
        database.users[user.id] = user;
      }
      const token = createShortLivedToken(user);
      return sendShortLivedToken(email, token);
    },
    createLongLivedToken: (_, { token }) => {
      return createLongLivedToken(token);
    },
  },
  Person: {
    __resolveType: (person) => {
      if (person.admin) {
        return "Admin";
      }
      return "User";
    },
  },
  User: {
    pins({ id }) {
      return Object.values(database.pins).filter((pin) => pin.user_id === id);
    },
  },
  SearchResult: {
    __resolveType: (searchResult) => {
      if (searchResult.admin) {
        return "Admin";
      }
      if (searchResult.email) {
        return "User";
      }
      return "Pin";
    },
  },
};

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 running node queries.js from a terminal. This script simulates a user who first creates an authentication token, and sends it in order to add a new pin.

$ node queries
API Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
  "data": {
    "addPin": {
      "id": "f5220ee1-bfeb-48a0-be9f-c63d055b8139",
      "title": "Hello world",
      "link": "https://example.com",
      "image": "https://example.com",
      "user_id": "75c16079-b3ef-43f0-a352-ae03f2488baa"
    }
  }
}

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.

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.

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.

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

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

const schema = require("./schema");

const server = new ApolloServer({ schema });

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});

That’s really it! With just a call to server.listen() you have a live GraphQL API. 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.

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.

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.

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.

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.

search: (_, { text }) => {
  return Promise.all([
    database("users").select().where("email", "like", `%${text}%`),
    database("pins").select().where("title", "like", `%${text}%`),
  ]);
};

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.

addPin: async (_, { pin }, { token }) => {
  const [user] = await authorize(token);
  const { user: updatedUser, pin: createdPin } = await addPin(user, pin);
  await database("pins").insert(createdPin);
  return createdPin;
},

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.

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

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:

module.exports = {
  client: "sqlite3",
  connection: {
    filename: ".data/database.sqlite",
  },
};

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.

exports.up = function (knex) {
  return knex.schema.createTable("users", function (table) {
    table.string("id").primary();
    table.string("email");
  });
};

exports.down = function (knex) {
  return knex.schema.dropTable("users");
};

PinApp needs one more migration, called [date]_create_pins_migration. It defines five string fields: id, title, link, image and pin_id.

exports.up = function (knex) {
  return knex.schema.createTable("pins", function (table) {
    table.string("id").primary();
    table.string("title");
    table.string("link");
    table.string("image");
    table
      .string("user_id")
      .references("id")
      .inTable("users")
      .onDelete("CASCADE")
      .onUpdate("CASCADE");
  });
};

exports.down = function (knex) {
  return knex.schema.dropTable("pins");
};

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

"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 authorization?”. 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.

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.

Feature: Email based authentication

  Scenario: Send magic link
    Given a user enters his email address
    When he/she submits the form
    Then he/she should receive an email with a link to the app that contains his token

  Scenario: Verify email
    Given a user receives an email with a magic link
    When he/she clicks the link
    Then he/she should see a loading screen
    When the app verifies the token
    Then he/she should see an email confirmation message

Speaking of GraphQL terms, both of these actions are mapped onto mutations in their corresponding 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.

type Mutation {
  # ...
  sendShortLivedToken(email: String!): Boolean
  createLongLivedToken(token: String!): String
}

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.

sendShortLivedToken: async (_, { email }) => {
  let user;
  const userExists = await database("users").select().where({ email });
  if (userExists.length) {
    user = userExists[0];
  } else {
    user = createUser(email);
    await database("users").insert(user);
  }
  const token = createShortLivedToken(user);
  return sendShortLivedToken(email, token);
};

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

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

const createShortLivedToken = ({ email, id }) => {
  return jsonwebtoken.sign({ id, email }, process.env.SECRET, {
    expiresIn: "5m",
  });
};

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

const sendShortLivedToken = (email, token) => {
  return sendMail({
    from: '"Julian" <julian@example.com>',
    to: email,
    text: `${process.env.APP_URL}/verify?token=${token}`,
    html: `<a href="${process.env.APP_URL}/verify?token=${token}" target="_blank">Authenticate</a>`,
    subject: "Auth token",
  });
};

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.

const nodemailer = require("nodemailer");

const transporter = nodemailer.createTransport({
  host: "smtp.ethereal.email",
  port: 587,
  auth: {
    user: process.env.MAIL_USER,
    pass: process.env.MAIL_PASSWORD,
  },
});

function sendMail({ from, to, subject, text, html }) {
  const mailOptions = {
    from,
    to,
    subject,
    text,
    html,
  };
  return new Promise((resolve, reject) => {
    transporter.sendMail(mailOptions, (error, info) => {
      if (error) {
        return reject(error);
      }
      resolve(info);
    });
  });
}

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.

const createLongLivedToken = (token) => {
  try {
    const { id, email } = jsonwebtoken.verify(token, process.env.SECRET);
    const longLivedToken = jsonwebtoken.sign(
      { id, email },
      process.env.SECRET,
      { expiresIn: "30 days" }
    );
    return Promise.resolve(longLivedToken);
  } catch (error) {
    console.error(error);
    throw error;
  }
};

Go ahead and configure your project with your Ethereal account. Once you have setup everything, start the server, open GraphQL Playground at the URL it prints 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 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:

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".

# import * from "authentication/schema.graphql"
# import * from "pins/schema.graphql"
# import * from "search/schema.graphql"

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

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

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

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.

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

const searchResolvers = require("./search/resolvers.js");
const authenticationResolvers = require("./authentication/resolvers.js");
const pinsResolvers = require("./pins/resolvers.js");

const resolvers = merge(
  searchResolvers,
  authenticationResolvers,
  pinsResolvers
);

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.

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.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:

import React from "react";

export default class App extends React.Component {
  render() {
    return <div>PinApp</div>;
  }
}

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.

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.

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.

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.

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.

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.

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:

import React from "react";
import {
  Container,
  Nav,
  PinListPage,
  AddPinPage,
  LoginPage,
  VerifyPage,
  ProfilePage,
} from "pinapp-components";

export default class App extends React.Component {
  state = { pins: [], authenticated: false, user: null };
  addPin = (pin) => {
    this.setState(({ pins }) => ({
      pins: pins.concat([pin]),
    }));
  };
  verify = () => {
    return success().then((token) =>
      this.setState({
        authenticated: true,
        user: { email: "name@example.com" },
      })
    );
  };
  authenticate = () => {
    return Promise.resolve({});
  };
  logout = () => {
    this.setState({ authenticated: false, user: null });
  };
  render() {
    return (
      <Container>
        <PinListPage pins={this.state.pins} />
        <AddPinPage
          authenticated={this.state.authenticated}
          addPin={this.addPin}
        />
        <LoginPage authenticate={this.authenticate} />
        <VerifyPage verify={this.verify} />
        <ProfilePage
          authenticated={this.state.authenticated}
          user={this.state.user}
          logout={this.logout}
        />
        <Nav authenticated={this.state.authenticated} />
      </Container>
    );
  }
}

function success() {
  return wait(1000).then(() => "long-lived-token");
}

function wait(time) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, time);
  });
}

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:

"dependencies": {
  "react-scripts": "^1.1.4",
  "react": "^16.3.2",
  "react-dom": "^16.3.2",
  "pinapp-components": "^1.0.1",
  "apollo-boost": "^0.1.6",
  "graphql": "^0.13.2",
  "graphql-tag": "^2.9.2"
},

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.

import ApolloClient from "apollo-boost";
import gql from "graphql-tag";

const client = new ApolloClient({
  uri: process.env.REACT_APP_API_URL,
});

Remember to add REACT_APP_API_URL=http://localhost:4000 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.

Add the following componentDidMount function to the App component:

componentDidMount() {
  client
    .query({
      query: gql`
        {
          pins {
            title
            image
            link
          }
        }
      `
    })
    .then(result => this.setState({ pins: result.data.pins }));
}

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.

import React from "react";
import { Query } from "react-apollo";
import { PinListPage, Spinner } from "pinapp-components";

import { LIST_PINS } from "./queries";

class PinListPageContainer extends React.Component {
  render() {
    return (
      <Query query={LIST_PINS}>
        {({ loading, error, data }) => {
          if (loading) {
            return <Spinner accessibilityLabel="Loading pins" show />;
          }
          if (error) {
            return <div>Error</div>;
          }
          return <PinListPage pins={data.pins} />;
        }}
      </Query>
    );
  }
}

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.

import gql from "graphql-tag";

export const ADD_PIN = gql`
  mutation AddPin($pin: PinInput!) {
    addPin(pin: $pin) {
      title
      link
      image
    }
  }
`;

export const LIST_PINS = gql`
  {
    pins {
      id
      title
      link
      image
      user_id
    }
  }
`;

export const CREATE_LONG_LIVED_TOKEN = gql`
  mutation CreateLongLivedToken($token: String!) {
    createLongLivedToken(token: $token)
  }
`;

export const CREATE_SHORT_LIVED_TOKEN = gql`
  mutation CreateShortLivedToken($email: String!) {
    sendShortLivedToken(email: $email)
  }
`;

export const ME = gql`
  {
    me {
      email
    }
  }
`;

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.

import { ApolloProvider } from "react-apollo";

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

import PinListPage from "./PinListPage";

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

<ApolloProvider client={client}>
  <Container>
    <PinListPage />
    {/* ... */}
  </Container>
</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 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”.

import React from "react";
import { Mutation } from "react-apollo";
import { LoginPage } from "pinapp-components";

import { CREATE_SHORT_LIVED_TOKEN } from "./queries";

class LoginPageContainer extends React.Component {
  render() {
    return (
      <Mutation mutation={CREATE_SHORT_LIVED_TOKEN}>
        {(createShortLivedToken) => (
          <LoginPage
            authenticate={(email) =>
              createShortLivedToken({
                variables: { email },
              })
            }
          />
        )}
      </Mutation>
    );
  }
}

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:

import React from "react";
import { Mutation } from "react-apollo";

import { AddPinPage } from "pinapp-components";
import { ADD_PIN, LIST_PINS } from "./queries";

class AddPinPageContainer extends React.Component {
  render() {
    return (
      <Mutation mutation={ADD_PIN}>
        {(addPin) => (
          <AddPinPage
            authenticated={this.props.authenticated}
            addPin={(pin) =>
              addPin({
                variables: { pin },
                refetchQueries: [{ query: LIST_PINS }],
              })
            }
          />
        )}
      </Mutation>
    );
  }
}

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.

import React from "react";
import { Mutation } from "react-apollo";
import { VerifyPage } from "pinapp-components";

import { CREATE_LONG_LIVED_TOKEN, ME } from "./queries";

class VerifyPageContainer extends React.Component {
  render() {
    return (
      <Mutation
        mutation={CREATE_LONG_LIVED_TOKEN}
        update={(cache, { data }) => {
          if (data && data.createLongLivedToken) {
            this.props.onToken(data.createLongLivedToken);
          }
        }}
      >
        {(createLongLivedToken) => (
          <VerifyPage
            verify={(shortLivedToken) =>
              createLongLivedToken({
                variables: {
                  token: shortLivedToken,
                },
                refetchQueries: [{ query: ME }],
              })
            }
          />
        )}
      </Mutation>
    );
  }
}

export default VerifyPageContainer;

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

<VerifyPage
  onToken={(token) => {
    localStorage.setItem("token", token);
    this.setState({ token });
  }}
/>

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:

import React from "react";
import { Query } from "react-apollo";
import { ProfilePage } from "pinapp-components";

import { ME } from "./queries";

class ProfilePageContainer extends React.Component {
  render() {
    if (!this.props.authenticated) {
      return (
        <ProfilePage
          authenticated={this.props.authenticated}
          logout={this.props.logout}
          user={null}
        />
      );
    }
    return (
      <Query query={ME}>
        {({ loading, error, data }) => {
          return (
            <ProfilePage
              authenticated={this.props.authenticated}
              logout={this.props.logout}
              user={{
                email: data && data.me ? data.me.email : null,
              }}
            />
          );
        }}
      </Query>
    );
  }
}

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:

import React from "react";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import { Container, Nav } from "pinapp-components";

import PinListPage from "./PinListPage";
import LoginPage from "./LoginPage";
import VerifyPage from "./VerifyPage";
import AddPinPage from "./AddPinPage";
import ProfilePage from "./ProfilePage";

const client = new ApolloClient({
  uri: process.env.REACT_APP_API_URL,
  request: (operation) => {
    if (this.state.token) {
      operation.setContext({
        headers: { Authorization: this.state.token },
      });
    }
  },
});

export default class App extends React.Component {
  state = {
    token: null,
  };
  componentDidMount() {
    const token = localStorage.getItem("token");
    if (token) {
      this.setState({ token });
    }
  }
  logout = () => {
    localStorage.removeItem("token");
    this.setState({ token: null });
  };
  render() {
    return (
      <ApolloProvider client={client}>
        <Container>
          <PinListPage />
          <AddPinPage authenticated={!!this.state.token} />
          <LoginPage />
          <VerifyPage
            onToken={(token) => {
              localStorage.setItem("token", token);
              this.setState({ token });
            }}
          />
          <ProfilePage
            authenticated={!!this.state.token}
            logout={this.logout}
          />
          <Nav authenticated={!!this.state.token} />
        </Container>
      </ApolloProvider>
    );
  }
}

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.

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.

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

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.

"dependencies": {
  // ...
  "pg": "^8.0.0"
}

Replace all the code in knexfile.js with this:

module.exports = {
  client: "pg",
  connection: process.env.DATABASE_URL,
};

Now, create a Postgres database. The easiest way to run one on your machine is with Docker. The following command starts Postgres in a container called pinapp-db, with a database called pinapp:

docker run -d --name pinapp-db -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=pinapp postgres:17

If port 5432 is already in use on your machine, change the first number in -p, for example -p 5433:5432, and use that port in the URLs below. You can also create a Postgres instance in any other way you feel comfortable with.

Place the database URL inside your project’s .env:

DATABASE_URL=postgres://postgres:postgres@localhost:5432/pinapp

Run npm run setup-db from a terminal to verify your database connection and set up all migrations.

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.

# ...
type Subscription {
  pinAdded: Pin
}

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.

Subscription: {
  pinAdded: {
    subscribe: () => {
      return pubsub.asyncIterator("pinAdded");
    };
  }
}

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

"dependencies": {
  "graphql-postgres-subscriptions": "^1.0.1"
}

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

const pubsub = new PostgresPubSub({
  connectionString: process.env.DATABASE_URL,
});

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

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

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

const { PostgresPubSub } = require("graphql-postgres-subscriptions");

const { addPin } = require("./index");
const { verify, authorize } = require("../authentication");
const database = require("../database");

const pubsub = new PostgresPubSub({
  connectionString: process.env.DATABASE_URL,
});

const resolvers = {
  Query: {
    pins: () => database("pins").select(),
  },
  Mutation: {
    addPin: async (_, { pin }, { token }) => {
      const [user] = await authorize(database, token);
      const { user: updatedUser, pin: createdPin } = await addPin(user, pin);
      await database("pins").insert(createdPin);
      pubsub.publish("pinAdded", { pinAdded: createdPin });
      return createdPin;
    },
  },
  Subscription: {
    pinAdded: {
      subscribe: () => {
        return pubsub.asyncIterator("pinAdded");
      },
    },
  },
};

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.

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

const schema = require("./schema");

const server = new ApolloServer({
  schema,
  context: async ({ req }) => {
    const context = {};
    if (req && req.headers && req.headers.authorization) {
      context.token = req.headers.authorization;
    }
    return context;
  },
  subscriptions: true,
});

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});

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.

{
  "Authorization": "eyJhbGciOiJIUzI..."
}

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…”.

subscription {
  pinAdded {
    title
    id
  }
}

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

mutation AddPin($pin: PinInput!) {
  addPin(pin: $pin) {
    title
  }
}

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

{
  "pin": {
    "title": "Hello subscriptions!",
    "link": "https://example.com",
    "image": "https://example.com"
  }
}

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

{
  "data": {
    "pinAdded": {
      "title": "Hello subscriptions!",
      "id": "cce1efda-b851-4272-b840-9aefbb84097f"
    }
  }
}

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 implementation 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.

const client = new ApolloClient({
  uri: process.env.REACT_APP_API_URL,
  request: (operation) => {
    if (this.state.token) {
      operation.setContext({
        headers: { Authorization: this.state.token },
      });
    }
  },
});

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:

"dependencies": {
  // ...
  "apollo-client": "^2.3.1",
  "apollo-cache-inmemory": "^1.2.1",
  "apollo-link-http": "^1.5.4",
  "apollo-link-error": "^1.0.9",
  "apollo-link": "^1.2.2"
}

Apollo client receives a link and cache properties. Setting up the cache will be much more straightforward 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.

// ...
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";

// ...

const client = new ApolloClient({
  cache: new InMemoryCache(),
});

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.

onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors)
    graphQLErrors.map(({ message, locations, path }) =>
      console.log(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
      )
    );
  if (networkError) console.log(`[Network error]: ${networkError}`);
});

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:

new ApolloLink((operation, forward) => {
  const request = async (operation) => {
    if (this.state.token) {
      operation.setContext({
        headers: { Authorization: this.state.token },
      });
    }
  };
  return new Observable((observer) => {
    let handle;
    Promise.resolve(operation)
      .then((oper) => request(oper))
      .then(() => {
        handle = forward(operation).subscribe({
          next: observer.next.bind(observer),
          error: observer.error.bind(observer),
          complete: observer.complete.bind(observer),
        });
      })
      .catch(observer.error.bind(observer));

    return () => {
      if (handle) handle.unsubscribe();
    };
  });
});

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.

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

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

import React from "react";
import { Container, Nav } from "pinapp-components";
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import { onError } from "apollo-link-error";
import { ApolloLink, Observable } from "apollo-link";

// ...

const client = new ApolloClient({
  link: ApolloLink.from([
    // Simulate Apollo Boost error handling
    onError(({ graphQLErrors, networkError }) => {
      if (graphQLErrors)
        graphQLErrors.map(({ message, locations, path }) =>
          console.log(
            `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
          )
        );
      if (networkError) console.log(`[Network error]: ${networkError}`);
    }),
    // Enable dynamic request interceptors
    new ApolloLink((operation, forward) => {
      const request = async (operation) => {
        if (this.state.token) {
          operation.setContext({
            headers: { Authorization: this.state.token },
          });
        }
      };
      return new Observable((observer) => {
        let handle;
        Promise.resolve(operation)
          .then((oper) => request(oper))
          .then(() => {
            handle = forward(operation).subscribe({
              next: observer.next.bind(observer),
              error: observer.error.bind(observer),
              complete: observer.complete.bind(observer),
            });
          })
          .catch(observer.error.bind(observer));

        return () => {
          if (handle) handle.unsubscribe();
        };
      });
    }),
    new HttpLink({
      uri: process.env.REACT_APP_API_URL,
      credentials: "same-origin",
    }),
  ]),
  cache: new InMemoryCache(),
});

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:

import { ApolloLink, Observable, split } from "apollo-link";
import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities";

// ...

const client = new ApolloClient({
  link: ApolloLink.from([
    // ...,
    // ...,
    split(
      // split based on operation type
      ({ query }) => {
        const { kind, operation } = getMainDefinition(query);
        return kind === "OperationDefinition" && operation === "subscription";
      },
      new WebSocketLink({
        uri: process.env.REACT_APP_API_URL.replace("https://", "wss://"),
        options: {
          reconnect: true,
        },
      }),
      new HttpLink({
        uri: process.env.REACT_APP_API_URL,
        credentials: "same-origin",
      })
    ),
  ]),
  cache: new InMemoryCache(),
});

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

export const PINS_SUBSCRIPTION = gql`
  subscription {
    pinAdded {
      title
      link
      image
      id
      user_id
    }
  }
`;

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.

class PinListPageContainer extends React.Component {
  componentDidMount() {
    this.props.subscribeToMore();
  }
  render() {
    return <PinListPage pins={this.props.pins} />;
  }
}

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.

export default () => (
  <PinListQuery>
    {({ pins, subscribeToMore }) => (
      <PinListPageContainer pins={pins} subscribeToMore={subscribeToMore} />
    )}
  </PinListQuery>
);

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 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 updateQuery. 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.

class PinListQuery extends React.Component {
  render() {
    return (
      <Query query={LIST_PINS}>
        {({ loading, error, data, subscribeToMore }) => {
          if (loading) {
            return <Spinner accessibilityLabel="Loading pins" show />;
          }
          if (error) {
            return <div>Error</div>;
          }
          const subscribeToMorePins = () => {
            return subscribeToMore({
              document: PINS_SUBSCRIPTION,
              updateQuery: (prev, { subscriptionData }) => {
                if (!subscriptionData.data || !subscriptionData.data.pinAdded) {
                  return prev;
                }
                const newPinAdded = subscriptionData.data.pinAdded;

                return Object.assign({}, prev, {
                  pins: [...prev.pins, newPinAdded],
                });
              },
            });
          };
          return this.props.children({
            pins: data.pins,
            subscribeToMore: subscribeToMorePins,
          });
        }}
      </Query>
    );
  }
}

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.

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.

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.

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.

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.

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

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

const server = new Server();

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});

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

"scripts": {
  "start": "node index.js",
  // ...
},

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.

const { ApolloServer } = require("apollo-server");
const { PostgresPubSub } = require("graphql-postgres-subscriptions");
const { Client } = require("pg");

const schema = require("./schema");
const createDatabase = require("./database");

class Server extends ApolloServer {
  constructor() {
    const database = createDatabase();
    const client = new Client({
      connectionString:
        process.env.NODE_ENV === "test"
          ? process.env.TEST_DATABASE_URL
          : process.env.DATABASE_URL,
    });
    client.connect();
    const pubsub = new PostgresPubSub({
      client,
    });
    super({
      schema,
      context: async ({ req }) => {
        const context = { database, pubsub };
        if (req && req.headers && req.headers.authorization) {
          context.token = req.headers.authorization;
        }
        return context;
      },
    });
    this.database = database;
    this.pubsub = pubsub;
  }
  stop() {
    return Promise.all([
      super.stop(),
      this.database.destroy(),
      this.pubsub.client.end(),
    ]);
  }
}

module.exports = Server;

You may have noticed a new database url, called TEST_DATABASE_URL. Create a second database for tests inside the same Postgres container:

docker exec pinapp-db createdb -U postgres pinapp_test

Then add it to .env as TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/pinapp_test.

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:

// ...
const resolvers = {
  Query: {
    users: async (_, __, { database }) => { /* */ },
    me: async (_, __, { token, database }) => { /* */ }
  },
  Mutation: {
    sendShortLivedToken: async (_, { email }, { database }) => { /* */ },
    createLongLivedToken: (_, { token }) => { /* */ }
  },
  Person: { /* */ },
  User: {
    pins(person, _, { database }) { /* */ }
  }

Modify pins/resolvers.js. Remove PostgresPubSub initialization, because it already is in server.js. Access pubsub and database from resolvers’ context.

const { addPin } = require("./index");
const { verify, authorize } = require("../authentication");

const resolvers = {
  Query: {
    pins: (_, __, { database }) => database("pins").select(),
  },
  Mutation: {
    addPin: async (_, { pin }, { token, database, pubsub }) => {
      /* */
    },
  },
  Subscription: {
    pinAdded: {
      subscribe: (_, __, { pubsub }) => {
        /* */
      },
    },
  },
};

module.exports = resolvers;

Modify search/resolvers.js:

const resolvers = {
  Query: {
    search: async (_, { text }, { database }) => {
      /* */
    },
  },
  SearchResult: {
    __resolveType: (searchResult) => {
      /* */
    },
  },
};

module.exports = resolvers;

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

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.

{
  "scripts": {
    // ...
    "test": "jest --watchAll --runInBand"
  },
  "devDependencies": {
    "jest": "^22.4.3"
  },

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 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:

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

const createDatabase = require("./database");
const schema = require("./schema");
const { search } = require("./queries");

describe("GraphQL layer", () => {
  let database;
  beforeAll(async () => {
    database = createDatabase();
    return database.seed.run();
  });
  afterAll(() => database.destroy());

  it("should return users' pins", () => {
    const query = `
  {
    users {
      id
      email
      pins {
        user_id
      }
    }
  }  
  `;
    return graphql(schema, query, undefined, { database }).then((result) => {
      expect(result.data.users).toMatchSnapshot();
    });
  });

  it("should list all pins", () => {
    const query = `
  {
    pins {
      id
      title
      link
      image
      user_id
    }
  }
  `;
    return graphql(schema, query, undefined, { database }).then((result) => {
      expect(result.data.pins).toMatchSnapshot();
    });
  });

  it("should search pins by title", () => {
    return graphql(
      schema,
      search,
      undefined,
      { database },
      { text: "First" }
    ).then((result) => {
      expect(result.data.search).toMatchSnapshot();
    });
  });
});

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 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.

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

const createDatabase = require("./database");
const schema = require("./schema");
const { search } = require("./queries");
const Server = require("./server");
const { deleteEmails } = require("./email");

describe("GraphQL layer", () => {
  /* */
});

describe("HTTP layer", () => {
  let server;
  let serverInfo;

  beforeEach(async () => {
    server = new Server();
    /*
      Ignore event emitter errors.
      In most cases this error appears because a database query got sent after closing database connection.
    */
    server.pubsub.ee.on("error", () => {});
    await Promise.all([
      server.database("users").del(),
      server.database("pins").del(),
    ]);
    serverInfo = await server.listen({ http: { port: 3001 } });
    deleteEmails();
  });

  afterEach(() => server.stop());

  // Tests
});

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.

const { graphql } = require("graphql");
const fetch = require("isomorphic-unfetch");

// ... Previous imports
const Server = require("./server");
const { deleteEmails } = require("./email");

describe("GraphQL layer", () => { /* */ });

describe("HTTP layer", () => {
  let server;
  let serverInfo;

  beforeEach(async () => { /* */ });

  afterEach(() => server.stop());

  it("should not allow unauthorized users to add pins", () => {
    const variables = {
      pin: {
        title: "Example",
        link: "http://example.com",
        image: "http://example.com"
      }
    };
    return fetch(serverInfo.url, {
      body: JSON.stringify({ query: addPin, variables }),
      headers: { "Content-Type": "application/json" },
      method: "POST"
    })
    .then(response => response.json())
    .then(response => {
      expect(response.errors).toMatchSnapshot();
    });
  });

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:

const nodemailer = require("nodemailer");

let transporter;

if (process.env.NODE_ENV === "test") {
  transporter = nodemailer.createTransport({
    jsonTransport: true,
  });
} else {
  transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    port: 587,
    auth: {
      user: process.env.MAIL_USER,
      pass: process.env.MAIL_PASSWORD,
    },
  });
}

function sendMail({ from, to, subject, text, html }) {
  const mailOptions = {
    from,
    to,
    subject,
    text,
    html,
  };
  return new Promise((resolve, reject) => {
    transporter.sendMail(mailOptions, (error, info) => {
      if (error) {
        return reject(error);
      }
      resolve(info);
    });
  });
}

module.exports = {
  sendMail,
};

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.

const nodemailer = require("nodemailer");

let transporter;
var emails = [];

if (process.env.NODE_ENV === "test") {
  /* */
} else {
  /* */
}

function sendMail({ from, to, subject, text, html }) {
  const mailOptions = {
    /* */
  };
  emails.push(mailOptions);
  return new Promise((resolve, reject) => {
    /* */
  });
}

function deleteEmails() {
  while (emails.length > 0) {
    emails.pop();
  }
}

module.exports = {
  emails,
  sendMail,
  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.

const { graphql } = require("graphql");
const fetch = require("isomorphic-unfetch");

// ... Previous imports
const { search, createShortLivedToken } = require("./queries");
const Server = require("./server");
const { deleteEmails, emails } = require("./email");

describe("GraphQL layer", () => {
  /* */
});

describe("HTTP layer", () => {
  let server;
  let serverInfo;

  beforeEach(async () => {
    /* */
  });

  afterEach(() => server.stop());

  // ...

  it("should allow users to create short lived tokens", () => {
    const email = "name@example.com";
    const variables = {
      email,
    };
    return fetch(serverInfo.url, {
      body: JSON.stringify({ query: createShortLivedToken, variables }),
      headers: { "Content-Type": "application/json" },
      method: "POST",
    })
      .then((response) => response.json())
      .then((response) => {
        expect(emails[emails.length - 1].to).toEqual(email);
      });
  });
});

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.

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.

const { graphql } = require("graphql");
const fetch = require("isomorphic-unfetch");
const url = require("url");

// ... Previous imports
const {
  search,
  createShortLivedToken,
  createLongLivedToken,
} = require("./queries");
const Server = require("./server");
const { deleteEmails, emails } = require("./email");
const { verify } = require("./authentication");

describe("GraphQL layer", () => {
  /* */
});

describe("HTTP layer", () => {
  let server;
  let serverInfo;

  beforeEach(async () => {
    /* */
  });

  afterEach(() => server.stop());

  // ...

  it("should allow users to create long lived tokens", () => {
    const email = "name@example.com";
    const variables = {
      email,
    };
    return fetch(serverInfo.url, {
      body: JSON.stringify({ query: createShortLivedToken, variables }),
      headers: { "Content-Type": "application/json" },
      method: "POST",
    })
      .then((response) => response.json())
      .then((response) => {
        const token = url.parse(emails[emails.length - 1].text, true).query
          .token;
        return fetch(serverInfo.url, {
          body: JSON.stringify({
            query: createLongLivedToken,
            variables: { token },
          }),
          headers: { "Content-Type": "application/json" },
          method: "POST",
        });
      })
      .then((response) => response.json())
      .then((response) => {
        expect(verify(response.data.createLongLivedToken).email).toEqual(email);
      });
  });
});

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.

it("should return authenticated user", () => {
  const email = "name@example.com";
  const variables = {
    email,
  };
  let token;
  return fetch(serverInfo.url, {
    body: JSON.stringify({ query: createShortLivedToken, variables }),
    headers: { "Content-Type": "application/json" },
    method: "POST",
  })
    .then((response) => response.json())
    .then((response) => {
      token = url.parse(emails[emails.length - 1].text, true).query.token;
      return fetch(serverInfo.url, {
        body: JSON.stringify({
          query: createLongLivedToken,
          variables: { token },
        }),
        headers: { "Content-Type": "application/json" },
        method: "POST",
      });
    })
    .then((response) => response.json())
    .then((response) => {
      return fetch(serverInfo.url, {
        body: JSON.stringify({ query: me }),
        headers: { "Content-Type": "application/json", Authorization: token },
        method: "POST",
      });
    })
    .then((response) => response.json())
    .then((response) => {
      expect(response.data).toMatchSnapshot();
    });
});

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.

it("should allow authenticated users to create pins", () => {
  const email = "name@example.com";
  const variables = {
    email,
  };
  let token;
  return fetch(serverInfo.url, {
    body: JSON.stringify({ query: createShortLivedToken, variables }),
    headers: { "Content-Type": "application/json" },
    method: "POST",
  })
    .then((response) => response.json())
    .then((response) => {
      token = url.parse(emails[emails.length - 1].text, true).query.token;
      return fetch(serverInfo.url, {
        body: JSON.stringify({
          query: createLongLivedToken,
          variables: { token },
        }),
        headers: { "Content-Type": "application/json" },
        method: "POST",
      });
    })
    .then((response) => response.json())
    .then((response) => {
      const pin = {
        title: "Example",
        link: "http://example.com",
        image: "http://example.com",
      };
      return fetch(serverInfo.url, {
        body: JSON.stringify({ query: addPin, variables: { pin } }),
        headers: { "Content-Type": "application/json", Authorization: token },
        method: "POST",
      });
    })
    .then((response) => response.json())
    .then((response) => {
      expect(response.data).toMatchSnapshot();
    });
});

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".

{
  "dependencies": {
    // ...
    "subscriptions-transport-ws": "^0.9.9"
  }
}

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.

const { graphql } = require("graphql");
const fetch = require("isomorphic-unfetch");
const url = require("url");
const { SubscriptionClient } = require("subscriptions-transport-ws");

// ...

describe("HTTP layer", () => {
  // ...
  it("should subscribe to pins", (done) => {
    const subscriptionClient = new SubscriptionClient(
      serverInfo.url.replace("http://", "ws://"),
      {
        reconnect: true,
        connectionCallback: (error) => {
          if (error) {
            done(error);
          }
        },
      }
    );
    subscriptionClient.on("connected", () => {
      subscriptionClient
        .request({
          query: pinsSubscription,
        })
        .subscribe({
          next: (result) => {
            expect(result).toMatchSnapshot();
            done();
          },
          error: done,
        });
      authenticateAndAddPin(serverInfo.url);
    });
    subscriptionClient.on("error", done);
  });
});

function authenticateAndAddPin(serverUrl) {
  const email = "name@example.com";
  const variables = {
    email,
  };
  let token;
  return fetch(serverUrl, {
    body: JSON.stringify({ query: createShortLivedToken, variables }),
    headers: { "Content-Type": "application/json" },
    method: "POST",
  })
    .then((response) => {
      token = url.parse(emails[emails.length - 1].text, true).query.token;
      const pin = {
        title: "Example",
        link: "http://example.com",
        image: "http://example.com",
      };
      return fetch(serverUrl, {
        body: JSON.stringify({ query: addPin, variables: { pin } }),
        headers: { "Content-Type": "application/json", Authorization: token },
        method: "POST",
      }).then((response) => response.json());
    })
    .then((response) => {
      if (response.errors) {
        throw new Error(response.errors[0].message);
      }
    });
}

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 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.

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:

import React from "react";
import ReactDOM from "react-dom";
import { MockedProvider } from "react-apollo/test-utils";
import * as ReactRouter from "react-router";
import * as ReactApollo from "react-apollo";

const MemoryRouter = ReactRouter.MemoryRouter;

ReactApollo.ApolloProvider = jest.fn(({ children }) => <div>{children}</div>);

import App from "./App";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(
    <MemoryRouter>
      <MockedProvider mocks={[]}>
        <App />
      </MockedProvider>
    </MemoryRouter>,
    div
  );
  ReactDOM.unmountComponentAtNode(div);
});

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 is not available in Node. Pass noRouter={process.env.NODE_ENV === "test"} to Container in src/App.js

// ...
export default class App extends React.Component {
  // ...
  render() {
    return (
      <ApolloProvider client={this.state.client}>
        <Container noRouter={process.env.NODE_ENV === "test"}>
          {/* */}
        </Container>
      </ApolloProvider>
    );
  }

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.

{
  "dependencies": {
    // ...
    "react-router": "^4.2.0"
  }
}

Run the test suite by running npm test from a terminal.

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.

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.

// ...
import {
  LIST_PINS,
  PINS_SUBSCRIPTION,
  CREATE_SHORT_LIVED_TOKEN,
  CREATE_LONG_LIVED_TOKEN,
  ME,
  ADD_PIN,
} from "./queries";

it("shows 'There are no pins yet' initially", async () => {
  const mocks = [
    {
      request: { query: LIST_PINS },
      result: {
        data: {
          pins: [],
        },
      },
    },
    {
      request: {
        query: PINS_SUBSCRIPTION,
      },
      result: { data: { pinAdded: null } },
    },
  ];
  const wrapper = mount(
    <MemoryRouter>
      <MockedProvider mocks={mocks}>
        <App />
      </MockedProvider>
    </MemoryRouter>
  );
  // Wait for async pins query
  await wait();
  // Manually update enzyme wrapper
  // https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#for-mount-updates-are-sometimes-required-when-they-werent-before)
  wrapper.update();
  expect(
    wrapper.contains((node) => node.text() === "There are no pins yet.")
  ).toBe(true);
  wrapper.unmount();
});

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.

it("should show a list of pins", async () => {
  const pins = [
    {
      id: "1",
      title: "Modern",
      link: "https://pinterest.com/pin/637540890973869441/",
      image:
        "https://i.pinimg.com/564x/5a/22/2c/5a222c93833379f00777671442df7cd2.jpg",
    },
    {
      id: "2",
      title: "Broadcast Clean Titles",
      link: "https://pinterest.com/pin/487585097141051238/",
      image:
        "https://i.pinimg.com/564x/85/ce/28/85ce286cba63daf522464a7d680795ba.jpg",
    },
    {
      id: "3",
      title: "Drawing",
      link: "https://pinterest.com/pin/618611698790230574/",
      image:
        "https://i.pinimg.com/564x/00/7a/2e/007a2ededa8b0ce87e048c60fa6f847b.jpg",
    },
  ];
  const mocks = [
    {
      request: { query: LIST_PINS },
      result: {
        data: {
          pins,
        },
      },
    },
    {
      request: {
        query: PINS_SUBSCRIPTION,
      },
      result: { data: { pinAdded: null } },
    },
  ];
  const wrapper = mount(
    <MemoryRouter>
      <MockedProvider mocks={mocks}>
        <App />
      </MockedProvider>
    </MemoryRouter>
  );
  await wait();
  wrapper.update();
  expect(wrapper.find(".pins .pin").length).toBe(3);
  wrapper.unmount();
});

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.

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.

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.

it("should allow users to login", async () => {
  const email = "name@example.com";
  const mocks = [
    {
      request: { query: LIST_PINS },
      result: {
        data: {
          pins: [],
        },
      },
    },
    {
      request: {
        query: PINS_SUBSCRIPTION,
      },
      result: { data: { pinAdded: null } },
    },
    {
      request: {
        query: CREATE_SHORT_LIVED_TOKEN,
        variables: {
          email,
        },
      },
      result: {
        data: {
          sendShortLivedToken: true,
        },
      },
    },
  ];
  const wrapper = mount(
    <MemoryRouter>
      <MockedProvider mocks={mocks}>
        <App />
      </MockedProvider>
    </MemoryRouter>
  );
  await wait();
  wrapper.update();
  expect(wrapper.find(".auth-banner").length).toBe(1);
  expect(wrapper.find('a[href="/profile"]').length).toBe(0);
  wrapper.find('a[href="/login"]').simulate("click", { button: 0 }); // Add { button: 0 } because of React Router bug https://github.com/airbnb/enzyme/issues/516
  wrapper.find("#email").first().prop("onChange")({ value: email });
  await wait();
  wrapper.update();
  wrapper.find("form").prop("onSubmit")({ preventDefault: () => {} });
  await wait();
  wrapper.update();
  expect(
    wrapper.contains(
      (node) =>
        node.text() === `We sent an email to ${email}. Please check your inbox.`
    )
  ).toBe(true);
  wrapper.unmount();
});

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.

it("should authenticate users who enter verify page", async () => {
  const email = "name@example.com";
  const token = "5minutes";
  const mocks = [
    {
      request: { query: LIST_PINS },
      result: {
        data: {
          pins: [],
        },
      },
    },
    {
      request: {
        query: PINS_SUBSCRIPTION,
      },
      result: { data: { pinAdded: null } },
    },
    {
      request: {
        query: CREATE_LONG_LIVED_TOKEN,
        variables: {
          token,
        },
      },
      result: {
        data: {
          createLongLivedToken: "30days",
        },
      },
    },
    {
      request: { query: ME },
      result: {
        data: {
          me: { email },
        },
      },
    },
  ];
  const initialEntries = [`/verify?token=${token}`];
  const wrapper = mount(
    <MemoryRouter initialEntries={initialEntries}>
      <MockedProvider mocks={mocks}>
        <App />
      </MockedProvider>
    </MemoryRouter>
  );
  await wait();
  wrapper.update();
  // Verify Page shows "Success!" for 1 second (1000 ms), then redirects to "/"
  await wait(1000);
  wrapper.update();
  wrapper.find('a[href="/profile"]').simulate("click", { button: 0 });
  await wait();
  wrapper.update();
  expect(
    wrapper.find(".profile-page").contains((node) => node.text() === email)
  ).toBe(true);
  wrapper.unmount();
});

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.

subscriptionsLink.simulateResult({
  result: {
    data: {
      pinAdded: {
        title,
        link,
        image,
        id: "1",
      },
    },
  },
});

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 MockSubscriptionsProvider 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.

// ...
import {
  MockedProvider,
  MockLink,
  MockSubscriptionLink,
} from "react-apollo/test-utils";
import { InMemoryCache as Cache } from "apollo-cache-inmemory";
import { getMainDefinition } from "apollo-utilities";
import { split } from "apollo-link";
import ApolloClient from "apollo-client";

const ApolloProvider = ReactApollo.ApolloProvider;
const MemoryRouter = ReactRouter.MemoryRouter;

ReactRouter.Router = jest.fn(({ children }) => <div>{children}</div>);
ReactApollo.ApolloProvider = jest.fn(({ children }) => <div>{children}</div>);

// ...

it("should allow logged in users to add pins", async () => {
  /* */
});

class MockedSubscriptionsProvider extends React.Component {
  constructor(props, context) {
    super(props, context);
    const subscriptionsLink = new MockSubscriptionLink();
    const addTypename = false;
    const mocksLink = new MockLink(props.mocks, addTypename);
    const link = split(
      // split based on operation type
      ({ query }) => {
        const { kind, operation } = getMainDefinition(query);
        return kind === "OperationDefinition" && operation === "subscription";
      },
      subscriptionsLink,
      mocksLink
    );
    const client = new ApolloClient({
      link,
      cache: new Cache({ addTypename }),
    });
    this.client = client;
    this.subscriptionsLink = subscriptionsLink;
  }
  render() {
    return (
      <ApolloProvider client={this.client}>
        {this.props.children}
      </ApolloProvider>
    );
  }
}

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);.

it("should allow logged in users to add pins", async () => {
  const title = "GraphQL College";
  const link = "https://example.com";
  const image = "https://example.com";
  const email = "name@example.com";
  const token = "5minutes";
  const mocks = [
    {
      request: { query: LIST_PINS },
      result: {
        data: {
          pins: [],
        },
      },
    },
    {
      request: {
        query: PINS_SUBSCRIPTION,
      },
      result: { data: { pinAdded: null } },
    },
    {
      request: {
        query: CREATE_LONG_LIVED_TOKEN,
        variables: {
          token,
        },
      },
      result: {
        data: {
          createLongLivedToken: "30days",
        },
      },
    },
    {
      request: { query: ME },
      result: {
        data: {
          me: { email },
        },
      },
    },
    {
      request: {
        query: ADD_PIN,
        variables: {
          pin: {
            title,
            link,
            image,
          },
        },
      },
      result: {
        data: {
          addPin: {
            title,
            link,
            image,
          },
        },
      },
    },
  ];
  const initialEntries = [`/verify?token=${token}`];
  const wrapper = mount(
    <MemoryRouter initialEntries={initialEntries}>
      <MockedSubscriptionsProvider mocks={mocks}>
        <App />
      </MockedSubscriptionsProvider>
    </MemoryRouter>
  );
  await wait();
  wrapper.update();
  await wait(1000);
  wrapper.update();
  wrapper
    .find('a[href="/upload-pin"]')
    .first()
    .simulate("click", { button: 0 });
  wrapper.update();
  wrapper.find('[placeholder="Title"]').first().prop("onChange")({
    target: { value: title },
  });
  wrapper.find('[placeholder="URL"]').first().prop("onChange")({
    target: { value: link },
  });
  wrapper.find('[placeholder="Image URL"]').first().prop("onChange")({
    target: { value: image },
  });
  wrapper.update();
  wrapper.find("form").prop("onSubmit")({ preventDefault: () => {} });
  const subscriptionsLink = wrapper
    .find(MockedSubscriptionsProvider)
    .instance().subscriptionsLink;
  subscriptionsLink.simulateResult({
    result: {
      data: {
        pinAdded: {
          title,
          link,
          image,
          id: "1",
        },
      },
    },
  });
  await wait(1000);
  wrapper.update();
  expect(wrapper.find(".pins .pin").length).toBe(1);
  wrapper.unmount();
});

Testing subscriptions is very straightforward once you can simulate results using MockSubscriptionLink.

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.

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.