Learning GraphQL and Relay

3 (2 reviews total)
By Samer Buna
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies

About this book

There’s a new choice for implementing APIs – the open source and Facebook-created GraphQL specification. Designed to solve many of the issues of working with REST, GraphQL comes alongside RelayJS, a React library for querying a server that implements the GraphQL specification. This book takes you quickly and simply through the skills you need to be able to build production ready applications with both GraphQL and RelayJS.

Beginning with a solid foundation in the GraphQl specification, this book swiftly moves to how a data layer can be implemented for your web application using Relay. Get to grips with GraphQL and Relay concepts creating data containers, data masking, and more as your progress towards building a production-ready application.

Publication date:
August 2016
Publisher
Packt
Pages
218
ISBN
9781786465757

 

Chapter 1. An Introduction to GraphQL and Relay

GraphQL provides a common interface between client and server applications for fetching and manipulating data. Relay is a JavaScript framework that uses GraphQL to enable React applications to communicate their data requirements in a declarative way.

This chapter will be an introduction to GraphQL and Relay. Here are the topics that we will cover in this chapter:

  • What is GraphQL, what is Relay, and what problems do they solve?

  • What is declarative data communication?

  • What does GraphQL look like and how does it work on the server?

  • What is a GraphQL schema?

  • How does GraphQL work with databases such as MongoDB?

  • How can GraphQL be used over HTTP?

 

What is GraphQL?


GraphQL is defined as a data query language and runtime. It's important to understand the two different parts of this definition:

  • GraphQL is a language. If we teach it to a software client application, that application will be able to declaratively communicate its data requirements to a backend data service that also speaks GraphQL.

  • GraphQL is a runtime. It's an execution layer a server application can use to understand and respond to any requests made with the GraphQL language. Think of this layer as simply a translator of the GraphQL language, or a GraphQL-speaking agent who represents the data service.

Anyone can invent a new language and start speaking it, but no one would understand them without learning the new language first, or having someone translate it for them. That's why we need to implement a runtime for GraphQL on the backend servers.

Backend servers speak their own languages, and they often speak multiple languages. A typical modern API server speaks at least two languages: one for the database, such as SQL, and another for processing the data, such as Java or Ruby.

GraphQL is designed to play well with other backend languages. We can implement GraphQL as a layer on top of any existing server logic. This layer will enable the server to understand GraphQL requests and pass them down to its existing logic to fetch data and fulfill the original requests.

Regardless of whether we're writing GraphQL requests for client applications, or GraphQL runtimes for server applications, we need to learn and understand the principles and standards of the GraphQL language first.

Learning the GraphQL language itself will be simple. It's not a big language, and it's very close to JavaScript Object Notation (JSON). If you're comfortable with JSON, you'll be able to pick up the GraphQL language in no time. If you don't know JSON, I'd recommend that you do some reading about it first. The Introducing JSON article at http://json.org/ is an excellent start.

Once we are fluent in the GraphQL language itself, we can use this new skill to translate for web and mobile applications. These applications have a constant need to communicate their data requirements to data services, and we can use the GraphQL language to do that for them.

Tip

Just like a child can learn a new language fast, while an adult will have a harder time picking it up, starting a new application from scratch using GraphQL will be a lot easier than introducing GraphQL to a mature application.

The GraphQL runtime layer, which can be written in any language, defines a generic graph-based schema to publish the capabilities of the data service it represents. Client applications can query the schema within its capabilities. This approach decouples clients from servers and allows both of them to evolve and scale independently.

A GraphQL operation can be either a query (read operation), or a mutation (write operation). For both cases, the operation is a simple string that a GraphQL service can parse and respond to with data in a specific format. The popular response format that is usually used for mobile and web applications is JSON.

Here's an example of a GraphQL query that a client can use to ask a server about the name and e-mail of user #42:

{ 
  user(id: 42) { 
    firstName 
    lastName 
    email 
  } 
} 

Here's a possible JSON response for that query:

{ 
  "data": { 
    "user": { 
      "firstName": "John", 
      "lastName": "Doe", 
      "email": "[email protected]" 
    } 
  } 
} 

The request and response in a GraphQL communication are related. A request determines the shape of its response, and a response can be used to easily construct a suitable request.

GraphQL on the server is just a specification that defines various design principles, including a hierarchical structure that supports composition, support of arbitrary code, a strong type system, introspective nature, and many more.

 

What is Relay?


Relay is defined as a framework for building data-driven React applications. It's important to understand the two aspects of Relay in this definition:

  • Relay is a framework, and it's actually an opinionated one. Opinionated frameworks make most of the design decisions for us, and they want us to do things a certain way. Depending on our requirements and conditions, this could be a great thing, but it could also be a great limitation. This applies to all frameworks, not just Relay.

  • Relay is for data-driven applications. When writing applications that are not driven by data, or applications that have minimal data interactions, Relay might not be the best choice.

However, most frontend applications are data-driven, even if at first we think otherwise. Simplify the problem domain, and it will most likely point to some data requirements.

Relay is a young and developing framework that is constantly changing. Its API syntax will most likely change in the near future; however, its concepts and design principles will stand the test of time.

Tip

Although Relay's concepts and principles can be applied to other view libraries, what Facebook open-sourced in 2015 as Relay is an extension to the React.js library, and it will only work with that library.

Here's an example React component with its Relay data requirements:

const UserCard = ({user}) => 
  <div className="user-card"> 
    Name  : {user.firstName} {user.lastName} 
    Email : {user.email} 
  </div>; 
 
UserCard = Relay.createContainer(UserCard, { 
  fragments: { 
    user: () => Relay.QL` 
      fragment on User { 
        firstName 
        lastName 
        email 
      } 
    ` 
  } 
}); 

This UserCard React component displays a user's name and e-mail, it requires the existence of a user object, and it requires that object to have the following properties: firstName, lastName, and email. Using a GraphQL fragment, we expressed this exact data requirement within a Relay container that wraps the UserCard React component. We'll explore GraphQL fragments in Chapter 2, The Query Language, and Relay containers in Chapter 4, Configuring React Applications to Use Relay.

Tip

Although this is a book about Relay and GraphQL together, it is very important to understand that they are separate projects, and the dependency between them is only unidirectional. Relay depends on GraphQL but a GraphQL server does not need Relay at all. While we can't run a Relay application without a GraphQL server, we can certainly run a GraphQL server without Relay's extensions or clients. GraphQL is not a part of any web or mobile frameworks; it operates independently, and it can be used by all frameworks. The first clients that used GraphQL at Facebook were the iOS and Android applications, a long time before Relay was born.

 

Why GraphQL?


Data communication is probably the most important activity in a software application. When was the last time you developed an application that did not communicate with a data service?

Even what we used to call static sites are now starting to be based on generators that use data. Games and other applications that load their data with their initial download also require some form of data communication afterwards to save preferences, track usages, and keep records about everything the user is doing. With the foreseen future of the Internet of Things, where micro devices will be everywhere, the role of data communication will become more important.

Relational databases successfully deliver on reliability, consistency, and integrity for the task of storing data. Document databases give us flexibility to manage document-oriented information, and scale it horizontally. However, the current solutions we use for communicating data between multiple software applications have many problems. We came up with all sorts of interfaces between applications and data services to fill the gaps. The most popular interfaces we use today are RESTful APIs and adhoc HTTP APIs. These interfaces are especially popular for web applications, but they've seen success with mobile applications as well.

Here are some of the tasks an Application Programming Interface (API) can do:

  • Act as a controller between protected raw data services and software clients

  • Parse a client request for access rights and enforce them

  • Construct SQL join statements to satisfy a client request efficiently

  • Process raw data into structures demanded by clients

  • Respond with data in specific formats such as JSON or XML

RESTful APIs versus GraphQL APIs

RESTful APIs are widely popular and have excellent use cases, but they also have limitations and disadvantages. They come with some dependencies on browser implementations of HTTP, and different browsers have different support for HTTP methods, and different interpretation for HTTP response codes. Using only HTTP methods and response codes limits what we can do with RESTful APIs and developers usually resort to customizing and interpreting the request payload instead.

In RESTful APIs, the language we use for the request is different than the language we use for the response. There is a disconnect between the request and the response, just like there is a disconnect between a question in English and an answer to that question in Japanese. There are no standards or agreements about what request and response HTTP codes mean and implementers use different specifications, which makes working with different APIs unpredictable. This lack of standards negatively affects the learning and development process around these APIs, and makes consuming them a challenge. Without standard specifications, developers need to consult documentation to understand the approach taken by every provider, and documentation is always at the risk of becoming outdated.

To consume RESTful APIs, we use a URL to read from or write to a single resource, such as a product, a person, a post, or a comment. If we need to work with multiple resources such as a list of posts with a list of comments, we need to use multiple endpoints. Alternatively, we can develop a custom endpoint (given that we have access to do so). The clients do not have any control over the response, unless we start customizing those endpoints to support that control. For example, we can't ask a friend resource endpoint for just the name and location of a friend, we can only ask for all the information about that friend, whether we need it or not. The clients basically depend on the servers, and this fact limits their growth because it will be tied to the growth of the servers.

Some of these issues are solved by other application programming interfaces such as JSON APIs, JSend, JSON LD, and many more. GraphQL is one other alternative that is attempting to solve most of these issues.

First, a draft GraphQL RFC specification has been created. It's managed by Facebook, but it's open source on GitHub and anyone can contribute to it. All GraphQL implementers are expected to honor that specification and work with the community to update the specification when needed.

GraphQL is protocol-agnostic and does not depend on anything HTTP. We don't use HTTP methods or HTTP response codes with GraphQL. However, HTTP is one channel where we can do GraphQL communication, and it will naturally be the popular channel for web development.

The language used for a GraphQL request is directly related to the language used for the response. If we analyze a JSON response, we'll find it to be a dictionary that has keys and values. The values can themselves be nested dictionaries with their own keys and values, or with arrays of values. Since the values basically represent the data, if we strip out all the values from the JSON dictionary, we get the GraphQL query that can be used for the request. This is a simple question-to-answer relationship that's expressed naturally with the same language. Since we use a similar language to communicate between clients and servers, debugging problems become easier. Furthermore, the GraphQL specification adopts a strong type system for all GraphQL elements; any misuse can be easily detected and properly reported. Also, with GraphQL queries mirroring the shape of their response, any deviations can be detected, and these deviations would point us to the exact query fields that are not resolving correctly.

A GraphQL server can be a single endpoint that handles all the client requests, and it can give the clients the power to customize those requests at any time. Clients can ask for multiple resources in the same request and they can customize the fields needed from all of them. This way, clients can be in control of the data they fetch and they can easily avoid the problems of over-fetching and under-fetching. With GraphQL, clients and servers are independent and they can be changed without affecting each other.

These are some of the reasons that make GraphQL efficient, effective, and easy to use. However, the most important reason why GraphQL is considered a game changer is its mental model around declarative data communication.

The idea of GraphQL was born out of practical needs, and these needs were mainly centered around mobile clients. Here are some examples of these needs:

  • Mobile clients are smart and have evolving data requirements, and we can't have them depend on a data service. We need to give them more power and have them decide what data to consume.

  • We can't control the versions of mobile applications like we do on the Web. We need a way to add new features without removing the old ones, and we need a way to communicate what features are now deprecated.

  • Resources available for mobile clients are limited. We can't entertain the idea of multiple round-trips to the server to collect the data required by a single view, and we need to minimize any processing needed to piece together data returned by servers.

However, I'd argue that the most important need that influenced the creation of GraphQL was not mobile-specific, but rather one that applies to all platforms:

  • The developer experience is as important as, and maybe actually more important than, the user experience. DI/DX is becoming the new UI/UX because the former drives the latter. When it comes to data communication, this means we need to abstract the imperative steps needed to communicate an application's data requirements, and give developers a declarative language for that instead. This language should enable developers to express their applications' data requirements in a way close to how that data will actually be used in their applications.

Putting the product developers' needs first means that instead of thinking about the proper ways to expose data on the servers, we first think about the developers who build frontend applications, and the proper ways for them to express their applications' data requirements.

That's why frontend application developers will love GraphQL. From their point of view, it's a query language that allows them to ask for the data required by their applications in a simple, natural, and declarative way that mirrors the way they use that data in their applications.

The needs that influenced GraphQL are really best explained with an example. Let's imagine that we are the developers responsible for building a shiny new user interface to represent the Star Wars films and characters.

The first UI we've been tasked to build is simple: a view to show information about a single Star Wars person, for example, Darth Vader. The view should display the person's name, birth year, planet name, and the titles of all the films in which they appeared.

As simple as that sounds, we're actually dealing with three different resources here: person, planet, and film. The relation between these resources is simple and anyone can guess the shape of the data here. A person object belongs to one planet object, and it will have one or more film objects.

The JSON data for this UI could be something like:

{ 
  "data": { 
    "person": { 
      "name": "Darth Vader", 
      "birthYear": "41.9BBY", 
      "planet": { 
        "name": "Tatooine" 
      }, 
      "films": [ 
        { "title": "A New Hope" }, 
        { "title": "The Empire Strikes Back" }, 
        { "title": "Return of the Jedi" }, 
        { "title": "Revenge of the Sith" } 
      ] 
    } 
  } 
} 

Assuming a data service gave us this exact structure for the data, here's one possible way to represent its view with React.js:

// The Container Component: 
<PersonProfile person={data.person}></PersonProfile> 
 
// The PersonProfile Component: 
Name: {person.name} 
Birth Year: {person.birthYear} 
Planet: {person.planet.name} 
Films: {person.films.map(film => film.title)} 

This is a simple example, and while our experience with Star Wars might have helped us here a bit, the relationship between the UI and the data is clear. The UI used all the keys from the assumed JSON data object.

Let's now see how we can ask for this data using a RESTful API.

We need a single person's information, and assuming that we know the id of that person, a RESTful API is expected to expose that information with an endpoint like:

/people/{id} 

This request will give us the name, birth year, and other information about the person. A good API will also give us the ID of this person's planet, and an array of IDs for all the films in which this person appeared.

Tip

Instead of IDs, RESTful APIs will usually give us the ready URLs that we need to follow to fetch more information about the resource. I am using IDs in the following examples to simplify the concept.

The JSON response for this request could be something like:

{ 
    "name": "Darth Vader", 
    "birthYear": "41.9BBY", 
    "planetId": 1, 
    "filmIds": [1, 2, 3, 6], 
    *** other information we do not need for this view *** 
} 

Then to read the planet's name, we ask:

/planets/1 

And to read the film titles, we ask:

/films/1 
/films/2 
/films/3 
/films/6 

Once we have all six responses from the server, we can combine them to satisfy the data needed by our view.

Besides the fact that we had to do six round trips to satisfy a simple data need for a simple UI, our approach here was imperative. We gave instructions for how to fetch the data and how to process it to make it ready for the view.

Note

A RESTful API for Star Wars data is currently hosted at http://swapi.co/. Go ahead and try to construct our data person object there; the field names might be a bit different, but the API endpoints should be the same. You will need to do exactly six API calls. Furthermore, you will have to over-fetch information that the view does not need.

Of course, this is just one implementation of a RESTful API for this data. There could be better implementations that will make this view easier to implement. For example, if the API server implemented nested resources and understood the relation between a person and a film, we could read the films data with:

/people/{id}/films 

However, API developers don't usually implement nested resource endpoints by default, and we would need to ask them to create these custom endpoints for us when we need them. That's the reality of scaling this type of API, we just add custom endpoints to efficiently satisfy the growing clients' needs. Managing custom endpoints like these without a structure around them would be a challenge.

Let's now look at the GraphQL approach. GraphQL on the server embraces the custom endpoints idea and takes it to its extreme. The server will be a single endpoint that replies to all data requests, and the interface channel does not matter. If we use an HTTP interface, HTTP methods and response codes would not matter either.

Let's assume we have a single GraphQL endpoint exposed over HTTP at /graphql. Since we want to ask for the data we need in a single round-trip, we'll need a way to express our complete data requirements for the server. We do this with a GraphQL query:

/graphql?query={...} 

A GraphQL query is just a string, but it will have to include all the pieces of the data that we need, and this is where the declarative power comes in.

In English, here's how we declare our data requirement: we need a person's name, birth year, their planet's name, and the titles of all their films.

In GraphQL, this translates to:

{ 
  person(ID: ...) { 
    name 
    birthYear 
    planet { 
      name 
    } 
    films { 
      title 
    } 
  } 
} 

Read the English-expressed requirements one more time and compare it to the GraphQL query. It's as close as it can get. Now compare this GraphQL query with the original JSON data that we started with:

{ 
  "data": { 
    "person": { 
      "name": "Darth Vader", 
      "birthYear": "41.9BBY", 
      "planet": { 
        "name": "Tatooine" 
      }, 
      "films": [ 
        { "title": "A New Hope" }, 
        { "title": "The Empire Strikes Back" }, 
        { "title": "Return of the Jedi" }, 
        { "title": "Revenge of the Sith" } 
      ] 
    } 
  } 
} 

The GraphQL query has the exact structure of the JSON data, except without all the values parts. If we think of this in terms of a question-answer relation, the question is the answer statement without the answer part.

If the answer statement is:

The closest planet to the Sun is Mercury.

A good representation of the question is the same statement without the answer part:

(What is) the closest planet to the Sun?

The same relation applies to a GraphQL query. Take a JSON response, remove all the answer parts (which are the values), and you'll get a GraphQL query very suitable to represent a question for that JSON response.

Now compare the GraphQL query with the React UI we defined for the data. Everything in the GraphQL query is used in the UI, and every variable used in the UI appears in the GraphQL query. This is the great mental model of GraphQL. The UI knows the exact data it needs, and extracting that requirement is fairly easy. Coming up with a GraphQL query is simply the task of extracting what's used as variables directly from the UI. Also, if we invert this model, it would still hold the power. If we have a GraphQL query, we know exactly how to use its response in the UI, because the query will be the same structure as the response. We don't need to inspect the response to know how to use it, and we don't need any documentation for the API; it's all built in.

A GraphQL API for Star Wars data is hosted at https://github.com/graphql/swapi-graphql. Go ahead and try to construct our data person object there. There are a few minor differences that we'll explain later, but here's the official query you can use against this API to read our data requirement for the view (with Darth Vader as an example):

{ 
  person(personID: 4) { 
    name 
    birthYear 
    homeworld { 
      name 
    } 
    filmConnection { 
      films { 
        title 
      } 
    } 
  } 
}

This request gives us a response structure very close to what our views used, and remember, we are getting all of this data in a single server round-trip. We'll explore the GraphiQL editor that you see in this API later in the chapter.

 

Why Relay?


React enabled us to create declarative UI views, and model the state for those views and not the transactions to render them. With React, we simply define views as functions of data.

Working with data, however, was the missing piece in the puzzle. Relay is one option to complete that puzzle and act as the data manager for React applications.

Just like React made declarative programming easier for building user interfaces, Relay can make declarative programming easier for fetching and mutating the data required for these user interfaces.

With Relay, we just declare what we need to happen to the data, and Relay will do the actual steps needed to satisfy our needs.

Working with data is always a challenge. We need to be aware of the performance issues around data communication. Are we making optimal requests, or are we making multiple requests that can be batched and further optimized? What should we do when a request fails? How do we handle errors? Should we retry a failed request? If we retry, when should we give up?

Furthermore, when users interact with a user interface and make some changes, we want them to see their changes reflected right away while we attempt to persist these changes to the database. We want to be able to either confirm the changes or roll them back once we have a response from the server.

There is also the task of paginating data in a smart way that can handle changed items. Think about the case where we fetched 10 items from a list, and before we fetch the next 10 items, an item was added or removed from the 10 items we originally fetched. Both the client and the server need to be aware of these edge cases.

There is also caching on both clients and servers. What can safely go to the cache and what cannot? How do we handle merging of cached data with new data? When do we expire things out of the cache? How do we cache related objects without duplicating them? Think about caching a comment, do we cache it under a post object, or do we cache it under an author object?

Working with data is the type of work that could be handled by a framework, and Relay is an attempt to innovate in that domain.

Understanding Relay's core principles

Here are some of the core principles and design decisions behind Relay.

Storage and caching

Relay uses a single normalized client-side data store in memory called Relay Store. When Relay loads up in the browser for the first time, it stores all data in this store, and it manages a simple state for every record in there.

In front of the Relay Store, Relay has a Queue Store where it manages the inflight changes to the data. The Queue Store allows us to do things, such as optimistic updates in the user interface. It also allows for easier rollbacks in case a change action fails. Instead of manually managing a state for the action, we can have Relay just remove the faulty object from the Queue Store. Behind the Relay Store, Relay has a Cache layer, which can be any storage engine, such as localStorage for example.

The hierarchy of these three layers is important in Relay because the first layer that can resolve a record will resolve that record.

Object identification

All objects in Relay have unique IDs over the entire system. This allows Relay to re-fetch any record when it needs to, and it prevents any ambiguity between objects inside of the Relay Store. Without proper unique IDs, duplicate records will find their way into the data store.

For example, imagine we have a client application where we show a list of users whose name begin with J, and for every user, we show their list of friends. User Jane who happens to also be a friend of John will be loaded twice in that view.

Relay also has a diffing algorithm to make data fetching as efficient as possible. If we already have part of an object's data, but need more, we don't need to ask for the whole object, we can ask for only the difference between what we need and what we have.

For example, in the same client application where we show a list of users, the initial list page shows the name and location of users, and when we click on a user record, we want to show their name, location, e-mail, and phone number. When we click on a user who is globally identified with a unique ID, we know that we already have their name and location in memory, so we can ask the data service for just their e-mail and phone number.

If we have another interface where we show a profile picture of the user along with their name and phone number, we would then need to ask the data service only about their profile picture.

The connection model

When we need to paginate a list, we have a few models we can use:

  • The offset/limit model: Let's say we have a list of comments for a blog post, sorted by creation date, most recent first. We have A through Z, and A is the most recent comment. To fetch the first three comments from that list, we do offset 0, limit 3, and we get A, B, and C. Before we ask for the next page, someone added a new comment, which now becomes the most recent comment on top of the list, before A. Our next page will be offset 3, limit 3 and we'd get C, D, and E. We have a duplicate C. There is also the case where after we received A, B, and C, someone deleted their embarrassing comment A, so now the next offset 3, limit 3 operation will get us E, F, and G, and we would miss D completely. This is not ideal.

  • The after/first model: For the same list of comments example, the first page, we do after null, first 3. We get A, B, and C. If someone deletes A, or adds a new comment before A, our next page would not be affected, because our next page would be after C, first 3, so we'll get back D, E, and F. This solves the problem mentioned in the offset/limit model. We need unique IDs for every record to make this model work, because we need to reference the records we see in the after value. It's no longer just numbers for pagination. One problem with this model is that there is no way to figure out whether there is more information to fetch or we just fetched the last available slice of comments. This means we need to do an extra request that returns an empty slice to conclude that we were on the last page.

  • The connection model: Relay extends the after/first model with a structure of edges and nodes, so that we can store extra information and metadata about the paginated data. The actual data is represented as nodes within edges. For example, we can use the top-level field to ask for the total number of records, or whether we have a next page or not. Every node gets a built-in cursor to identify it, and we can use that cursor in our after/first calls. We don't need to manually manage a unique ID for every record.

 

Setting up a simple GraphQL server


Since GraphQL is a specification for a server runtime, we can use any language to create a GraphQL schema and build an interface around it.

There are many GraphQL implementations that we can use today to create a GraphQL schema. There are GraphQL implementations for JavaScript, Java, Ruby, Scala, Python, and many more. Some GraphQL implementations will even allow us to ask for data directly from SQL databases.

We will be using the GraphQL JavaScript implementation (graphql-js) to create our schema. It's a complete reference implementation that is well-documented and easy to work with. It's also open source and easy to read. It's currently hosted at https://github.com/graphql/graphql-js.

In order to use JavaScript on the server side, we will need to install Node.js first. Node.js allows us to execute JavaScript code on the server using the same engine that powers the Chrome browser, V8.

Installing Node.js

To install Node.js, we can download and use the binary installers from https://nodejs.org/en/ On Mac and Linux, we can also set up Node.js using Node Version Manager (NVM). NVM targets the task of managing multiple Node.js versions, but it also uses a user-install by default, which keeps everything related to Node.js in one directory. With NVM, it's also easier to update current Node.js installations with newer versions of Node.js when they become available.

NVM does not work on Microsoft Windows, but there are alternatives such as the nvm-windows project, and the nodist project. The Windows installer available on the Node.js website is also another good option for Microsoft Windows users.

Tip

I will be assuming a Linux-based shell environment in the commands and outputs presented in this book. In Microsoft Windows environments, commands and outputs might be slightly different. In all the command lines, the part before the $ sign will be the working directory where the command was issued, and the part after the $ sign is the command itself. For example, with the line:

~ $ ls

This means run the command ls in the home directory ~.

To install NVM (on Mac or Linux), we can execute the following command:

~ $ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.4/install.sh | bash

The command downloads the install.sh script from GitHub (using curl), then it executes the script with the bash command. The script clones the NVM repository to ~/.nvm and adds a line to the profile file (~/.bash_profile, ~/.zshrc or ~/.profile) to prepare the NVM environment.

Tip

This command uses version 0.31.4 of NVM, which was the latest version at the time of writing. Check the NVM repository on GitHub to see the latest: https://github.com/creationix/nvm.

If we open a new terminal window now, we should have the nvm command line available in the environment. To verify:

~ $ nvm --version 
0.31.4

Once we have the nvm command available, we can use it to install the latest Node.js:

~ $ nvm install node 
Downloading https://nodejs.org/dist/v6.3.1/node-v6.3.1-darwin-x64.tar.gz... 
Creating default alias: default -> node (-> v6.3.1) 

This command installs the latest Node available. At the time of writing, it was 6.3.1; you can also instruct nvm to install Node v-6.3.1 instead using:

~ $ nvm install 6.3.1

NVM will make a few Node.js commands available for us. The most important ones are:

  • node: we use this command to execute a JavaScript file on the server, for example: node script.js. If we invoke the node command by itself without a target to execute, it runs in REPL mode (Read, Eval, Print, Loop). We can use this mode to test simple JavaScript one-liners while we are in the terminal.

  • npm: we use this command to install, uninstall, or update a Node.js package.

The GraphQL JavaScript reference implementation is published as an npm package under the name graphql. We can use npm to install it locally for our projects.

Defining the schema

Let's create a new directory for our project:

~ $ mkdir ~/graphql-project 
~ $ cd ~/graphql-project

We're going to make our project depend on the GraphQL JavaScript library. To document this dependency, we need to create a package.json file in our project. A package.json file is used by npm to store information about the project, including any dependencies on other packages. We can use the npm init command to create the package.json file:

~/graphql-project $ npm init

The npm init command will ask a few questions about the project, and it will use our answers to create a package.json file. The default answers are usually a good start.

With a package.json file created, we can now bring in the graphql library using this command:

~/graphql-project $ npm install --save graphql 
└─┬ [email protected]

This command will do two things:

  • Download the graphql library from https://www.npmjs.com/ and make it available locally under a node_modules directory in our project

  • Update the package.json file to document this new dependency. This is because we used the --save option

Tip

When committing the changes made so far to source control, remember to ignore this newly created node_modules directory. I use Git, and with Git, we can just add a node_modules line to a .gitignore file. Chapter 8, Deploying to the Cloud, has instructions about Git.

With the graphql npm package available locally, we can now write our schema. Create a schema directory at the root level of our project and create a main.js file there. We will define our GraphQL schema object in this main.js file:

~/graphql-project $ mkdir schema && touch schema/main.js

The first thing we need to do in main.js is to import a few classes from the graphql package:

const { 
  GraphQLSchema, 
  GraphQLObjectType, 
  GraphQLString 
} = require('graphql'); 

Note

We will be using the Node.js require syntax to import dependencies. The official JavaScript syntax for this task is different, it uses an import statement instead, but this official syntax is not supported by Node.js yet (as of 6.3.1). You'll find a lot of examples on the Web that use the import syntax, and use Babel to compile it into the require syntax which Node.js can work with. In future versions of Node.js, the import syntax will be supported natively:

// Instead of:
const graphql = require('graphql');
// We can do:
import graphql from 'graphql';

We imported only the three helpers that we will be using in the first example, but there are many other helpers we can import form the graphql library. We will import the other helpers later when we need them.

Tip

The examples demonstrated in this introduction chapter are only to give you a taste of the powerful features in GraphQL. We're only exploring a few features though, and more features will be introduced in later chapters. You will also most likely see syntax that is new to you; the GraphQL language syntax will be covered in detail in Chapter 2, The Query Language, and the GraphQL schema syntax will be covered in detail in Chapter 3, The GraphQL Schema.

GraphQLSchema is the class we can use to instantiate our example schema. Let's name this example schema object mySchema:

const mySchema = new GraphQLSchema({ 
  // root query & root mutation definitions 
}); 

A GraphQL schema can expose multiple capabilities. If we want clients to be able to ask for data, we need to define a query property on the schema. If we want to support any kind of insert, update, or delete operations on our data, we need to define a mutation property on the schema.

The query and mutation properties are instances of the GraphQLObjectType class. Let's start with a simple example query. Under the helpers we defined in schema/main.js, define a queryType object:

const queryType = new GraphQLObjectType({ 
  name: 'RootQuery', 
  fields: { 
    hello: { 
      type: GraphQLString, 
      resolve: () => 'world' 
    } 
  } 
}); 

Under the queryType definition, update the mySchema object to use queryType for its query configuration property:

const mySchema = new GraphQLSchema({ 
  query: queryType 
}); 

We start by assigning the queryType object as the query property of the GraphQLSchema instance (mySchema). queryType is an instance of GraphQLObjectType and we give it the name RootQuery. The name can be anything of course.

The fields property on a GraphQL object is where we define the fields that can be used in a GraphQL query to ask about that object. We define an example hello field, which is a GraphQLString.

Every field in a GraphQL object can define a resolve() function. The resolve() function is what the graphql library executes when it tries to answer queries asking about that field. For our simple example, we resolve that hello field with the string world.

Finally, to be able to use this simple schema from other parts of our application, we need to export it:

module.exports = mySchema; 

Here's the complete schema/main.js file so far which defines and exports the mySchema object:

const { 
  GraphQLSchema, 
  GraphQLObjectType, 
  GraphQLString 
} = require('graphql'); 
 
const queryType = new GraphQLObjectType({ 
  name: 'RootQuery', 
  fields: { 
    hello: { 
      type: GraphQLString, 
      resolve: () => 'world' 
    } 
  } 
}); 
 
const mySchema = new GraphQLSchema({ 
  query: queryType 
}); 
 
module.exports = mySchema; 

Note

I am going to maintain a GitHub repository for this book. This repo will have incremental commits, branches, and tags associated with chapters and sections of the book. You can clone the repo from https://github.com/edgecoders/learning-graphql-and-relay and check your progress against the git tag for the chapter's section.

For example, after the Defining the schema section here in this chapter, you can git checkout chapter1-defining-the-schema to see the code as it is exactly at this point in the chapter.

Every point in the book that has a matching git tag in the repo will have a #GitTag line referencing that tag. Compare your code to the code in the repo to debug any problems you run into. The repo will also have branches for each chapter; if you want to check out the code as it was at the end of Chapter 4, Configuring React Applications to Use Relay, for example, you can git checkout chapter4.

If you have comments or questions, feel free to submit a GitHub issue on the repo.

*** #GitTag: chapter1-defining-the-schema ***

Using the schema

Our schema wouldn't be any good unless we could execute queries against it. To do so, we need to create an interface between the user and the schema. This interface will be used to take input from the user (a GraphQL query), and give the user an output (a GraphQL JSON response).

The simplest interface we can use here is a regular Linux command. We can use its standard input (stdin) to supply a GraphQL request, and its standard output (stdout) to respond with the GraphQL server answer for the requested query.

In the root level of our project, create an index.js file with this content:

const { graphql } = require('graphql'); 
const readline = require('readline'); 
 
const mySchema = require('./schema/main'); 
 
const rli = readline.createInterface({ 
  input: process.stdin, 
  output: process.stdout 
}); 
 
rli.question('Client Request: ', inputQuery => { 
  graphql(mySchema, inputQuery).then(result => { 
    console.log('Server Answer :', result.data); 
  }); 
 
  rli.close(); 
}); 

Tip

readline is one option of many we can use to interface our GraphQL schema, and it's not a required package to use with GraphQL. We used it here to make for a simple example.

We start by importing the graphqllibrary and the readline library. We also need to use our GraphQL schema that we defined earlier (mySchema), so we import that as well.

We need to create an interface that understands stdin and stdout, and then just use that interface (rli) with a .question() function, which takes a string argument and a callback argument.

The Client Request: string is what the interface will display as an input prompt when we execute the script. In the second argument, which is a callback, readline will expose what the user types as the inputQuery variable.

The graphql() function we imported on the first line can then be used to execute the user's GraphQL query against our defined schema. If our server receives a valid query that it can understand, it will respond with a promise that resolves to a JSON response for that query. To use the standard out (stdout) for our interface, we can use console.log to log the promise-resolved JSON result.

To test this index.js script, we can execute it with the node command:

~/graphql-project $ node index.js 
Client Request: { hello } 
Server Answer : { hello: 'world' }

Our script starts by prompting the user to enter input for Client Request. We typed the string { hello }. This string represents a client asking about the hello root field that we defined in our schema.

The server's answer is a JSON string that matches our request query. It uses the resolve() function's returned value (world) as the value for the hello field:

*** #GitTag: chapter1-using-the-schema ***

Rolling the dice

Let's add another capability to our GraphQL Server. We will make it simulate a simple two-dice roll.

We'll design our server to be able to respond to the following query:

Client Request: { diceRoll } 

We expect the server to reply with two random values between 1 and 6:

Server Answer : { diceRoll: [2, 5] } 

The data type of our planned response is an array. In the graphql library, we use a GraphQLList to represent an array type, and we will use a GraphQLInt type to represent the elements of our random integers array. GraphQLList and GraphQLInt are both helpers available from the graphql library.

Update the require line in schema/main.js and add the two new helpers:

const { 
  // ... 
  GraphQLInt, 
  GraphQLList 
} = require('graphql'); 

We need a function that can respond with a random number between 1 and 6. To accomplish that, we can use a combination of Math.random and Math.floor. Add the following function right after the require line in schema/main.js:

const roll = () => Math.floor(6 * Math.random()) + 1; 

In the mySchema object, we need to add a new root field, diceRoll, to our list of fields and make its type a GraphQLList of GraphQLInt:

fields: { 
  // The hello field definition... 
  diceRoll: { 
    type: new GraphQLList(GraphQLInt), 
    resolve: () => [roll(), roll()] 
  } 
} 

Our diceRoll field resolves with an array that has two integers, which are both computed from a roll() function call. The type for our resolved value here is a GraphQLList, and each item in that list is a GraphQLInt.

To test this new capability, we will execute the same babel-node as previously command.

Here are three different dice rolls to make sure we are getting random values:

~/graphql-project $ node index.js 
Client Request: { diceRoll } 
Server Answer : { diceRoll: [ 3, 6 ] } 
 
~/graphql-project $ node index.js 
Client Request: { diceRoll } 
Server Answer : { diceRoll: [ 6, 6 ] } 
 
 ~/graphql-project $ node index.js 
Client Request: { diceRoll } 
Server Answer : { diceRoll: [ 3, 2 ] }
*** #GitTag: chapter1-rolling-the-dice ***

Using field arguments

We want to make our server capable of rolling more than two dice. In fact, we want the clients to be able to customize their question and tell us how many dice to roll.

We can utilize a GraphQL field argument for that purpose. The client can ask a query, such as:

{ diceRoll(count: 5) } 

For the above query, the client expects the server to roll five dice. Here's how we can change the diceRoll field definition to allow for that operation on the server:

    diceRoll: { 
      type: new GraphQLList(GraphQLInt), 
      args: { 
        count: { type: GraphQLInt } 
      }, 
      resolve: (_, args) => { 
        let rolls = []; 
        for (let i = 0; i < args.count; i++) { 
          rolls.push(roll()); 
        } 
        return rolls; 
      } 
    } 

To make our GraphQL server aware of the arguments passed to a field, we define an args property on that field with the name and type of the allowed argument. For our example, count is a GraphQLInt.

We can use the same roll() function to generate one random value, but since we're now receiving a count from the user, we need to dynamically construct an array using that count value. Inside the resolve() function, all the arguments that get passed to that field in a client request can be accessed using the second argument for the resolve() function itself. We can read the value the user enters for the count argument, using args.count.

In a simple for loop, we construct an array of args.count length, and fill it with a different roll() call for each element.

We named the first argument for resolve() with an underscore because we're not using it for this example. This argument represents the parent object and it's undefined on the first root-level queries. We'll learn about arguments for the resolver functions in Chapter 3, The GraphQL Schema.

To test this new feature:

~/graphql-project $ node index.js 
 
Client Request: { diceRoll(count: 5) } 
Server Answer : { diceRoll: [ 6, 5, 3, 5, 1 ] }

Tip

Note that this change makes the code depend on the count argument. If we want the code to work with or without a count argument, we should add a default value for that argument. In GraphQL, we can define a default value for any argument using the optional defaultValue property:

args: { 
  count: { 
    type: GraphQLInt, 
    defaultValue: 2 
  } 
}
 *** #GitTag: chapter1-using-field-arguments ***

Setting up MongoDB

An API is nothing without access to a database. Let's set up a local MongoDB instance, add some data in there, and make sure we can access that data through our GraphQL schema.

MongoDB can be locally installed on multiple platforms. Check the documentation site for instructions for your platform (https://docs.mongodb.com/manual/installation/).

For Mac, the easiest way is probably Homebrew:

~ $ brew install mongodb 

Create a db folder inside a data folder. The default location is /data/db:

~ $ sudo mkdir -p /data/db

Change the owner of the /data folder to be the current logged-in user:

~ $ sudo chown -R $USER /data

Start the MongoDB server:

~ $ mongod 

Tip

In Chapter 8, Deploying to the Cloud, we go over instructions to install MongoDB on an Ubuntu machine.

If everything worked correctly, we should be able to open a new terminal and test the mongo CLI:

~/graphql-project $ mongo 
 
MongoDB shell version: 3.2.8 
connecting to: test 
> db.getName() 
test 
>

Tip

We're using MongoDB version 3.2.8 here. Make sure that you have this version or newer versions of MongoDB.

Let's go ahead and create a new collection to hold some test data. Let's name that collection users:

> db.createCollection("users")" 
{ "ok" : 1 }

Now we can use the users collection to add documents that represent users. We can use the MongoDB insertOne() function for that:

> db.users.insertOne({ 
    firstName: "John", 
    lastName: "Doe", 
  })

We should see an output like this:

{ 
  "acknowledged" : true, 
  "insertedId" : ObjectId("56e729d36d87ae04333aa4e1") 
}

Let's go ahead and add another user:

> db.users.insertOne({ 
    firstName: "Jane", 
    lastName: "Doe", 
  })

We can now verify that we have two user documents in the users collection:

> db.users.count() 
2

Tip

MongoDB has a built-in unique object ID which you can see in the output for insertOne(). We will be using this object ID later in the book.

Now that we have a running MongoDB, and we have some test data in there, it's time to see how we can read this data using a GraphQL API.

To communicate with a MongoDB from a Node.js application, we need to install a driver. There are many options that we can choose from, but GraphQL requires a driver that supports promises. We will use the official MongoDB Node.js driver, which supports promises. Instructions on how to install and run the driver can be found at https://docs.mongodb.com/ecosystem/drivers/node-js/.

To install the MongoDB official Node.js driver under our graphql-project app, we do this:

~/graphql-project $ npm install --save mongodb 
└─┬ [email protected]

We can now use this mongodb npm package to connect to our local MongoDB server from within our Node application. In index.js, add the following:

const { MongoClient } = require('mongodb'); 
const assert = require('assert'); 
 
const MONGO_URL = 'mongodb://localhost:27017/test'; 
 
MongoClient.connect(MONGO_URL, (err, db) => { 
  assert.equal(null, err); 
  console.log('Connected to MongoDB server'); 
 
  // The readline interface code 
}); 

Tip

The MONGO_URL variable value should not be hardcoded in code like this. Instead, we can use a Node process environment variable to set it to a certain value before executing the code. On a production machine, we would be able to use the same code and set the process environment variable to a different value.

Use the export command to set the environment variable value: export MONGO_URL=mongodb://localhost:27017/test

Then in the Node code, we can read the exported value by using this:

 process.env.MONGO_URL

If we now execute the node index.js command, we should see the Connected to MongoDB server line right before we ask for the Client Request.

Tip

At this point, the Node.js process will not exit after our interaction with it. We'll need to force exit the process with Ctrl + C to restart it.

Let's start our database API with a simple field that can answer this question: how many total users do we have in the database?

The query could be something like this:

{ usersCount } 

To be able to use a MongoDB driver call inside our schema main.js file, we need access to the db object that the MongoClient.connect() function exposed for us in its callback. We can use the db object to count the user documents by simply running the promise:

db.collection('users').count() 
  .then(usersCount => console.log(usersCount)); 

Since we only have access to the db object in index.js within the connect() function's callback, we need to pass a reference to that db object to our graphql() function. We can do that using the fourth argument for the graphql() function, which accepts a contextValue object of globals, and the GraphQL engine will pass this context object to all the resolver functions as their third argument. Modify the graphql function call within the readline interface in index.js to be:

graphql(mySchema, inputQuery, {}, { db }).then(result => { 
  console.log('Server Answer :', result.data); 
  db.close(() => rli.close()); 
}); 

Tip

The third argument to the graphql() function is called the rootValue, which gets passed as the first argument to the resolver function on the top level type. We are not using that feature here.

We passed the connected database object db as part of the global context object. This will enable us to use db within any resolver function.

Note also how we're now closing the rli interface within the callback for the operation that closes the db. We should not leave any open db connections behind.

Here's how we can now use the resolver third argument to resolve our usersCount top-level field with the dbcount() operation:

fields: { 
  // "hello" and "diceRoll"... 
  usersCount: { 
    type: GraphQLInt, 
    resolve: (_, args, { db }) =>       
      db.collection('users').count() 
  } 
} 

A couple of things to notice about this code:

  • We destructured the db object from the third argument for the resolve() function so that we can use it directly (instead of context.db).

  • We returned the promise itself from the resolve() function. The GraphQL executor has native support for promises. Any resolve() function that returns a promise will be handled by the executor itself. The executor will either successfully resolve the promise and then resolve the query field with the promise-resolved value, or it will reject the promise and return an error to the user.

We can test our query now:

~/graphql-project $ node index.js 
Connected to MongoDB server 
Client Request: { usersCount } 
Server Answer : { usersCount: 2 }
*** #GitTag: chapter1-setting-up-mongodb ***

Setting up an HTTP interface

Let's now see how we can use the graphql() function under another interface, an HTTP one.

We want our users to be able to send us a GraphQL request via HTTP. For example, to ask for the same usersCount field, we want the users to do something like this:

/graphql?query={usersCount} 

We can use the Express.js node framework to handle and parse HTTP requests, and within an Express.js route, we can use the graphql() function, for example:

const app = express(); 
 
app.use('/graphql', (req, res) => { 
  // use graphql() to respond with JSON objects 
}); 

However, instead of manually handling the req/res objects, there is a GraphQL Express.js middleware that we can use, express-graphql. This middleware wraps the graphql() function and prepares it to be used by Express.js directly. Let's go ahead and bring in both the Express.js library and this middleware:

~/graphql-project $ npm install --save express express-graphql 
├─┬ [email protected] 
└─┬ [email protected]

In index.js, we can now import both express and the express-graphql middleware:

const graphqlHTTP = require('express-graphql'); 
const express = require('express'); 
 
const app = express(); 

With these imports, the middleware main function will now be available as graphqlHTTP(). We can now use it in an Express route handler. Inside the MongoClient.connect() callback, we can do this:

  app.use('/graphql', graphqlHTTP({ 
    schema: mySchema, 
    context: { db } 
  })); 
 
  app.listen(3000, () => 
    console.log('Running Express.js on port 3000') 
  ); 

Tip

Note that at this point, we can remove the readline interface code as we are no longer using it. Our GraphQL interface from now on will be an HTTP endpoint.

The app.use line defines a route at /graphql and delegates the handling of that route to the express-graphql middleware that we imported. We pass two objects to the middleware, the mySchema object and the context object. We're not passing any input query here because this code just prepares the HTTP endpoint, and we will be able to read the input query directly from a URL field.

Tip

The app.listen() function is the call we need to start our Express.js app. Its first argument is the port to use, and its second argument is a callback we can use after Express.js has started.

We can now test our HTTP-mounted GraphQL executor with:

~/graphql-project $ node index.js 
 
Connected to MongoDB server 
Running Express.js on port 3000 

In a browser window, go to:

http://localhost:3000/graphql?query={usersCount} 

*** GitTag: chapter1-setting-up-an-http-interface ***

The GraphiQL editor

The graphqlHTTP() middleware function accepts another property on its parameter object graphiql; let's set it to true:

app.use('/graphql', graphqlHTTP({ 
  schema: mySchema, 
  context: { db }, 
  graphiql: true 
})); 

When we restart the server now and navigate to http://localhost:3000/graphql, we'll get an instance of the GraphiQL editor running locally on our GraphQL schema:

GraphiQL is an interactive playground where we can explore our GraphQL queries and mutations before we officially use them. GraphiQL is written in React and GraphQL, and it runs completely within the browser.

GraphiQL has many powerful editor features such as syntax highlighting, code folding, and error highlighting and reporting. Thanks to GraphQL introspective nature, GraphiQL also has intelligent type-ahead for fields, arguments, and types.

Put the cursor in the left editor area, and type a selection set:

{ 
} 

Place the cursor inside that selection set and press Ctrl + space. You should see a list of all fields that our GraphQL schema supports, which are the three fields that we have defined so far (hello, diceRoll, and usersCount):

Tip

If Ctrl + space does not work, try Cmd+ space, Alt + space, or Shift + space.

The __schema and __type fields can be used to introspectively query the GraphQL schema about what fields and types it supports. We will talk about GraphQL introspection in Chapter 3, The GraphQL Schema.

When we start typing, this list starts to get filtered accordingly. The list respects the context of the cursor; for example, if we place the cursor inside the arguments of diceRoll(), we'll get the only argument we defined for diceRoll, the count argument.

Go ahead and read all the root fields that our schema support, and see how the data gets reported on the right side with a formatted JSON object:

*** GitTag: chapter1-the-graphiql-editor ***
 

Summary


In this chapter, we introduced GraphQL and Relay and talked about what they are, what problems they solve, and why they are needed. We saw examples of GraphQL queries and how they get used in Relay. We talked about RESTful APIs, and how they compare to GraphQL APIs. Using an example user interface, we learned how a simple GraphQL API makes a big difference in how we communicate with a data service.

We talked about Relay, its core principles, how it handles storage and caching, and how it uniquely identifies every object in an application. We also talked about Relay's connection model for pagination.

We learned how to create a simple GraphQL schema, and how to use it with different interfaces, including an HTTP interface. We also learned how to use GraphQL to read data from MongoDB. We explored the GraphiQL editor and used it to inspect our GraphQL schema and read the data it exposes.

About the Author

  • Samer Buna

    Samer Buna is a technical content author, software engineer, and mentor. He has a master's degree in information security and over ten years of progressive experience and success creating tailored solutions for businesses within many industries.

    Samer is passionate about everything JavaScript, and he loves exploring new libraries. His favorite technical stacks are Node.js for the backend and React.js for the frontend.

    Samer has authored a few books and online courses about React and GraphQL. You can follow him on Twitter at @samerbuna, and you can read more of what he writes at https://edgecoders.com/.

    Browse publications by this author

Latest Reviews

(2 reviews total)
Ich benutze seit Jahren die RESTful Technik und das eBook über GraphQL sollte mich überzeugen das diese neue Query Lösung mehr Wert ist als die gute einfache bewährte RESTful industrie Standard. Die Grátchenfrage ist in diesem Fall mehr als berechtigt wer braucht so viele oft so komplzierte neue Sprache, der Benutzer oder der Erfinder
Pas assez d'infos sur Relay.

Recommended For You

Book Title
Unlock this full book FREE 10 day trial
Start Free Trial