Customizing and organizing GraphQL operations
This chapter covers
-
Using arguments to customize what a request field returns
-
Customizing response property names with aliases
-
Describing runtime executions with directives
-
Reducing duplicated text with fragments
-
Composing queries and separating data requirement responsibilities
In any nontrivial application, you have to do many things beyond asking the server a direct, simple, single-value question. Data fetching is usually coupled with variables and meta-questions about the structure of the response. You often need to modify the data returned by the server to make it suitable for your application. Sometimes you have to remove parts of the data or go back to the server and ask for other parts that are required based on conditions in your application. You also need a way to organize big requests and categorize them to know which part of your application is responsible for each part of your requests. Luckily, the GraphQL language offers many built-in features you can use to do all of this and much more. These customizing and organizing features are what this chapter is all about.
1. Customizing fields with arguments
The fields in a GraphQL operation are similar to functions. They map input to output. A function input is received as a list of argument values. Just like functions, we can pass any GraphQL field a list of argument values. A GraphQL schema on the backend can access these values and use them to customize the response it returns for that field.
Let’s look at use cases for these field arguments and some examples used by the GitHub GraphQL API (az.dev/github-api).
1.1. Identifying a single record to return
Every API request that asks for a single record from a collection needs to specify an identifier for that record. This identifier is usually associated with a unique identifier for that record in the server’s database, but it can also be anything else that can uniquely identify the record.
For example, if you ask an API for information on a single user, you usually send along with your request the ID of the user you are interested in. You can also send their email address, username, or Facebook ID connection if, for example, you are logging them in through a Facebook button.
Here is an example query that asks for information about the user whose email address is [email protected]
query UserInfo { user(email: "[email protected]") { firstName lastName userName } }
The email
part inside the user
field is called a field argument.
Note that for an API field representing a single record, the argument value you pass to identify that record must be a unique value on that field record in the database. For example, you cannot pass the person’s full name to identify their user record because the database might list many people who have the same name.
However, you can pass multiple arguments to identify the user. For example, you can pass a full name and an address to uniquely identify a single person.
Examples of single-record fields are popular. Some GraphQL APIs even have a single-record field for every object in the system. This is commonly known in the GraphQL world as a Node interface: a concept popularized by the Relay framework (which also originated at Facebook). With a Node interface, you can look up any node in the data graph by its unique global system-wide ID. Then, based on what that node is, you can use an inline fragment to specify the properties on that node that you are interested in seeing in the response.
query NodeInfo { node(id: "A-GLOBALLY-UNIQUE-ID-HERE") { ...on USER { firstName lastName userName email } } }
See section 4 later in this chapter for more details about the inline-fragment in listing 3.2.
In the GitHub API, some examples of single-record fields are user
, repository
, project
, organization
, and many more. Here is an example to read information about the jsComplete organization, which hosts all open source resources for jsComplete.com.
query OrgInfo { organization(login: "jscomplete") { name description websiteUrl } }
1.2. Limiting the number of records returned by a list field
When you ask for a list of records from a collection, a good API will always ask you to provide a limit. How many records are you interested in?
It is usually a bad idea to leave a general API capability that enables listing records in a collection without a limit. You do not want a client to be able to fetch more than a few hundred records at a time, because that would put your API server at risk of resource exhaustion and does not scale well. This is exactly why the GitHub API requires the use of an argument like first
(or last
) when you ask it for a list of records. Go ahead and try to ask for all the repositories under the jsComplete organization. You can use the repositories
field within the organization
field in the OrgInfo
query in listing 3.3. You should see that GitHub asks for a first
or last
value, as shown in figure 3.1.

first
or last
argument.Since any list of records in a database has a certain order, you can limit your request results using either end of that order. If you are interested in 10 records, you can get the first 10 records or the last 10 records using these arguments.
Here is the query you can use to retrieve the first 10 repositories under the jsComplete organization.
query First10Repos { organization(login: "jscomplete") { name description websiteUrl repositories(first: 10) { nodes { name } } } }
By default, the GitHub API orders the repositories in ascending order by date of creation. You can customize that ordering logic with another field argument.
1.3. Ordering records returned by a list field
In the previous example, the GitHub API ordered the list of repositories under the jsComplete organization by the CREATED_AT
repository order field. The API supports many other order fields, including UPDATED_AT
, PUSHED_AT
, NAME
, and STARGAZERS
.
Here is a query to retrieve the first 10 repositories when they are ordered alphabetically by name.
query orgReposByName { organization(login: "jscomplete") { repositories(first: 10, orderBy: { field: NAME, direction: ASC }) { nodes { name } } } }
Can you use the GitHub field arguments you learned about find the top-10 most popular repositories under the jsComplete organization? Base a repository’s popularity on the number of stars it has.
Here is one query you can use to do that.
query OrgPopularRepos { organization(login: "jscomplete") { repositories(first: 10, orderBy: { field: STARGAZERS, direction: DESC }) { nodes { name } } } }
1.4. Paginating through a list of records
When you need to retrieve a page of records, in addition to specifying a limit, you need to specify an offset. In the GitHub API, you can use the field arguments after
and before
to offset the results returned by the arguments first
and last
, respectively.
To use these arguments, you need to work with node identifiers, which are different than database record identifiers. The pagination interface that the GitHub API uses is called the Connection interface (which originated from the Relay framework as well). In that interface, every record is identified by a node
field (similar to the Node interface) using a cursor
field. The cursor is basically the ID field for each node, and it is the field we use with the before
and after
arguments.
To work with every node’s cursor next to that node’s data, the Connection
interface adds a new parent to the node concept called an edge. The edges
field represents a list of paginated records, and every object in that list holds a unique cursor value.
Here is a query that includes cursor values through the edges
field.
query OrgRepoConnectionExample { organization(login: "jscomplete") { repositories(first: 10, orderBy: { field: CREATED_AT, direction: ASC }) { edges { cursor node { name } } } } }
Note that within an edges
field, we now ask about a single node
field because the list is no longer a list of nodes but rather a list of edges where each edge is a node + cursor.
Now that you can see the string values of these cursors, you can use them as the after
and before
arguments to fetch extra pages of data. For example, to fetch the second page of the repositories under the jsComplete organization, you need to identify the cursor of the last repository on the first page (which was advanced-nodejs when I tested these queries) and use that cursor value as the after value.
query OrgRepoConnectionExample2 { organization(login: "jscomplete") { repositories( first: 10, after: "Y3Vyc29yOnYyOpK5MjAxNy0wMS0yMVQwODo1NTo0My0wODowMM4Ev4A3", orderBy: { field: CREATED_AT, direction: ASC } ) { edges { cursor node { name } } } } }
The introduction of the edges
field also allows the addition of metadata about the list. For example, on the same level where we ask for a list of edges, we can also ask how many total records this relation has and whether there are more records to fetch after the current page. Here is the previous query modified to show some metadata about the relation.
query OrgReposMetaInfoExample { organization(login: "jscomplete") { repositories( first: 10, after: "Y3Vyc29yOnYyOpK5MjAxNy0wMS0yMVQwODo1NTo0My0wODowMM4Ev4A3", orderBy: { field: STARGAZERS, direction: DESC } ) { totalCount pageInfo { hasNextPage } edges { cursor node { name } } } } }
Since the jsComplete organization has more than 20 repositories (2 pages, in this example), the hasNextPage
field is true
. When you fetch the very last page, hasNextPage
will return false
, indicating that there is no more data to fetch. This is much better than having to do an extra empty page fetch to conclude that you have reached the end of the paginated data.
1.5. Searching and filtering
A field argument in GraphQL can be used to provide filtering criteria or search terms to limit the results returned by a list. Let’s see examples for both features.
In GitHub, a repository can have a list of projects to manage any work related to that repository. For example, the Twitter Bootstrap repository on GitHub started using a project per release to manage all the issues related to a single release. Here is a query that uses a search term within the projects relation to return the Twitter Bootstrap projects that start with "v4.1".
query SearchExample { repository(owner: "twbs", name: "bootstrap") { projects(search: "v4.1", first: 10) { nodes { name } } } }
Note that the projects
field also implements the Connection interface.
Some fields allow you to filter the returned list by certain properties of that field. For example, under the GitHub viewer
field, you can see a list of all your repositories. By default, that list includes all the repositories that you own or can contribute to. To list only the repositories that you own, you can use the affiliations
field argument.
query FilterExample { viewer { repositories(first: 10, affiliations: OWNER) { totalCount nodes { name } } } }
1.6. Providing input for mutations
The field arguments concept is what GraphQL mutations use to accept the mutation operation’s input. In the previous chapter, we used the following mutation example to add a star to the GraphQL-In-Action repository under the jsComplete organization.
mutation StarARepo { addStar(input: { starrableId: "MDEwOlJlcG9zaXRvcnkxMjU2ODEwMDY=" }) { starrable { stargazers { totalCount } } } }
The input
value in that mutation is also a field argument. It is a required argument. You cannot perform a GitHub mutation operation without an input
object. All GitHub API mutations use this single required input
field argument that represents an object. To perform a mutation, you pass the various input values as key/value pairs on that input
object.
Not all arguments are required. A GraphQL API can control which arguments are required and which are optional. |
There are many more cases where a field argument can be useful. Explore the GitHub API and other publicly available GraphQL APIs for more useful patterns for field arguments.
2. Renaming fields with aliases
The alias feature in a GraphQL operation is very simple but powerful because it allows you to customize a response coming from the server through the request itself. By using aliases, you can minimize any post-response processing on the data.
Let me explain this with an example. Let’s say you are developing the profile page in GitHub. Here is a query to retrieve partial profile information for a GitHub user.
query ProfileInfo { user(login: "samerbuna") { name company bio } }
You get a simple user object in the response (see figure 3.2).

ProfileInfo
query asking for a company
fieldNow an application UI can use this user
object in the query’s response to substitute for the values in a UI template. However, suppose you have just discovered a mismatch between the structure of your response and the structure the application UI is using for the user
object. The application UI was developed to expect a companyName
field on a user instead of a company
field (as found in the API response). What do you do? Assume that changing the application UI code itself is not an option.
If you do not have the option to use an alias (I will show you how in a bit), your can process the response every time you use the response object. You need to transform the user
object from the response into a new object with the right structure. This can be costly if the structure you are working with deep and has multiple levels.
Luckily, in GraphQL, the awesome alias feature lets us declaratively instruct the API server to return fields using different names. All you need to do is specify an alias for that field, which you can do using this syntax:
aliasName: fieldName
Just prefix any field name with an alias, and the server will return that field renamed using your alias. There is no need to process the response object. For the example in listing 3.13, all you need to do is specify a companyName
alias.
query ProfileInfoWithAlias { user(login: "samerbuna") { name companyName: company bio } }
This gives a response that is ready for you to plug into the application UI (see figure 3.3).

companyName
field from the server3. Customizing responses with directives
Sometimes, the customization you need on a server response goes beyond simple renaming of fields. You may need to conditionally include (or exclude) branches of data in your responses. This is where the directives feature of GraphQL can be helpful.
A directive in a GraphQL request is a way to provide a GraphQL server with additional information about the execution and type validation behavior of a GraphQL document. It is essentially a more powerful version of field arguments: you can use directives to conditionally include or exclude the entire field. In addition to fields, directives can be used with fragments and top-level operations.
A directive is any string in a GraphQL document that begins with the @
character. Every GraphQL schema has three built-in directives: @include
, @skip
, and @deprecated
. Some schemas have more directives. You can use this introspective query to see the list of directives supported by a schema.
query AllDirectives { __schema { directives { name description locations args { name description defaultValue } } } }
This query shows the name and description of each directive and includes an array of all possible locations where that directive can be used (figure 3.4). In addition, it lists all arguments supported by that directive. Each directive can optionally receive a list of arguments, and just like field arguments, some argument values may be required by the API server. The response to the previous query should show that in any GraphQL schema, both the @include
and @skip
directives have the argument if
. The @deprecated
directive has the argument reason
.

The list of locations in the previous query’s response is also important. Directives can be used only in the locations they are declared to belong to. For example, the @include
and @skip
directives can only be used after fields or fragments. You cannot use them at the top level of an operation. Similarly, the @deprecated
directive can only be used after field definitions or ENUM
values when defining a GraphQL service schema.
An ENUM (enumerated) type represents a set of possible unique values. We’ll see an example in the next chapter.
|
Since directives are usually used with arguments, they are often paired with query variables to have them sent with a dynamic value. We saw some examples of variables in the previous chapter, but let me remind you about them.
3.1. Variables and input values
A variable is simply any name in the GraphQL document that begins with a $
sign: for example, $login
or $showRepositories
. The name after the $
sign can be anything.
We use variables to make GraphQL operations generically reusable and avoid having to hardcode values and concatenate strings.
To use a variable in a GraphQL operation, you first need to define its type. You do that by providing arguments to any named operation. For example, let’s take the query example that we used to read information about the jsComplete organization on GitHub. Instead of hardcoding the login value (as we did before), let’s now use a variable. The operation must have a name, and then we can use that name’s arguments to define any variables. Let’s call the variable $orgLogin
. It should be a required string. You can see the type of the arguments using the Docs explorer. Look up the organization
field to see the types of its login
argument. You can also click the organization
field in the query while holding the Command key (Ctrl in Windows).
As you can see in figure 3.5, the login
argument has a type of String!
. The trailing exclamation mark on that type is GraphQL’s way of labeling the argument value as required. A value for this login
argument must be specified — it cannot be null.

Now we can use the same syntax to define the new variable. The type for $orgLogin
should match the type of the argument where it is going to be used. Here is the same query written with this new $orgLogin
variable.
query OrgInfo($orgLogin: String!) { organization(login: $orgLogin) { name description websiteUrl } }
Note that on the first line, the query specifies that $orgLogin
is also a String!
.
You cannot execute the query in listing 3.16 as is. If you try, the GraphQL server will return an error. Since we used a variable, we must give the executor on the server the value that we wish to use as that variable. In GraphiQL, we do that using the variables editor, which is in the lower-left corner. In that editor, you write a JSON object that represents all variables you want to send to the executor along with the operation.
Since we used only one variable, the JSON object for that is
{ "orgLogin": "jscomplete" }
Now you can execute the query with different JSON objects, making it reusable for different organizations (see figure 3.6).

A variable like $orgLogin
can also have a default value, in which case it does not need the trailing exclamation mark. You specify the default value using an equal sign after the type of the variable. For example, the previous query can have the value "jscomplete" as the default value of $orgLogin
using this syntax.
query OrgInfoWithDefault($orgLogin: String = "jscomplete") { organization(login: $orgLogin) { name description websiteUrl } }
You can execute this OrgInfoWithDefault
query without passing a JSON object for variables. The query will use the default value in that case. If you pass a JSON object with a value for "orgLogin", that value will override the default value.
Variables can be used in fields and directives to make them accept input values of various literal primitives. An input value can be scalar, like Int
, Float
, String
, Boolean
, or Null
. It can also be an ENUM
value, a list, or an object. The $orgLogin
variable represents a scalar string input value for the login
argument within the organization
field. Read the various GraphQL operation examples we have seen so far and try to identify more input values. For example, try to find where we used an object as an input value.
Now that we know how to define and use variables and values, let’s use them with directives.
3.2. The @include directive
The @include
directive can be used after fields (or fragments) to provide a condition (using its if
argument). That condition controls whether the field (or fragment) should be included in the response. The use of the @include
directive looks like this:
fieldName @include(if: $someTest)
This says to include the field when the query is executed with $someTest
set to true
and not to include the field when $someTest
is set to false
. Let’s look at an example from the GitHub API.
Building on the previous OrgInfo
query example, let’s assume that we want to conditionally include an organization’s websiteUrl
based on whether we are showing full or partial details in the UI. Let’s design a Boolean variable to represent this flag and call it $fullDetails
.
This new $fullDetails
variable will be required because we are about to use it with a directive. The first line of the OrgInfo
query needs to be changed to add the type of $fullDetails
:
query OrgInfo($orgLogin: String!, $fullDetails: Boolean!) {
Now we want to include the websiteUrl
only when we execute the OrgInfo
query with $fullDetails
set to true
. A simple use of the @include
directive can do that. The if
argument value in this case will be the $fullDetails
variable. Here is the full query.
@include
directive (az.dev/gia)query OrgInfo($orgLogin: String!, $fullDetails: Boolean!) { organization(login: $orgLogin) { name description websiteUrl @include(if: $fullDetails) } }
Go ahead and test this query by executing it with $fullDetails
set to true
and false
. You will see that the response honors that Boolean value and uses it to include or exclude websiteUrl
from the response object (see figure 3.7).

3.3. The @skip directive
This directive is simply the inverse of the @include
directive. Just like the @include
directive, it can be used after fields (or fragments) to provide a condition (using its if
argument). The condition controls whether the field (or fragment) should be excluded in the response. The use of the @skip
directive looks like this:
fieldName @skip(if: $someTest)
This means to exclude the field when the query is executed with $someTest
set to true
and include the field when $someTest
is set to false
. This directive is useful to avoid negating a variable value, especially if that variable has a negative name already.
Suppose that instead of designing the Boolean variable to be $fullDetails
, we decide to name it $partialDetails
. Instead of inverting that variable value in the JSON values object, we can use the @skip
directive to use the $partialDetails
value directly. The OrgInfo
query becomes the following.
@skip
directive (az.dev/gia}query OrgInfo($orgLogin: String!, $partialDetails: Boolean!) { organization(login: $orgLogin) { name description websiteUrl @skip(if: $partialDetails) } }
Note that a field (or fragment) can be followed by multiple directives. You can repeat @include
multiple times or even use both @include
and @skip
together. All directive conditions must be met for the field (or fragment) to be included or excluded.
Neither @include
nor @skip
has precedence over the other. When used together, a field is included only when the include condition is true and the skip condition is false; it is excluded when either the include condition is false or the skip condition is true. The following query will never include websiteUrl
no matter what value you use for $partialDetails
.
query OrgInfo($orgLogin: String!, $partialDetails: Boolean!) { organization(login: $orgLogin) { name description websiteUrl @skip(if: $partialDetails) @include(if: false) } }
3.4. The @deprecated directive
This special directive can be used in GraphQL servers to indicate deprecated portions of a GraphQL service’s schema, such as deprecated fields on a type or deprecated ENUM
values.
When deprecating a field in a GraphQL schema, the @deprecated
directive supports a reason
argument to provide the reason behind the deprecation. The following is the GraphQL’s schema language representation of a type that has a deprecated field.
@deprecated
directivetype User { emailAddress: String email: String @deprecated(reason: "Use 'emailAddress'.") }
The @deprecated directive can only be used on the server side.
|
4. GraphQL fragments
When we explored directives, I kept adding "(or fragment)" whenever I mentioned the use of a directive. It is now time to discuss my favorite feature of the GraphQL language: fragments!
4.1. Why fragments?
To build anything big or complicated, the only truly helpful strategy is to split what needs to be built into smaller parts and then focus on one part at a time. Ideally, the smaller parts should be designed in a way that does not couple them with each other. They should be testable on their own, and they should also be reusable. A big system should be the result of putting these parts together and having them communicate with each other to form features.
For example, in the UI domain, the React.js library popularized the idea of using small components to build a full UI. React.js certainly was not the first library to use the concept of components, but it did make most UI developers aware of their benefits.
In GraphQL, fragments are the composition units of the language. They provide a way to split big GraphQL operations into smaller parts. A fragment in GraphQL is simply a reusable piece of any GraphQL operation.
I like to compare GraphQL fragments to UI components. Fragments, if you will, are the components of a GraphQL operation.
Splitting a big GraphQL document into smaller parts is the main advantage of GraphQL fragments. However, fragments can also be used to avoid duplicating a group of fields in a GraphQL operation. We will explore both benefits, but let’s first understand the syntax for defining and using fragments.
4.2. Defining and using fragments
To define a GraphQL fragment, you can use the fragment
top-level keyword in any GraphQL document. You give the fragment a name and specify the type on which that fragment can be used. Then, you write a partial query to represent the fragment.
For example, let’s take the simple GitHub organization information query example:
query OrgInfo { organization(login: "jscomplete") { name description websiteUrl } }
To make this query use a fragment for the organization
fields, you first need to define the fragment.
fragment orgFields on Organization { name description websiteUrl }
This defines an orgFields
fragment that can be used within a selection set that expands an organization
object. The on Organization
part of the definition in listing 3.22 is called the type condition of the fragment. Since a fragment is essentially a selection set, you can only define fragments on object types. You cannot define a fragment on a scalar value.
To use the fragment, you "spread" its name where the fields were originally used in the query.
query OrgInfoWithFragment { organization(login: "jscomplete") { ...orgFields } }
The three dots before orgFields
in listing 3.23 are what you can use to spread orgFields
. The concept of spreading a fragment is similar to the concept of spreading an object in JavaScript. The same three-dots operator can be used in JavaScript to spread an object inside another object, effectively cloning that object.
The three-dotted fragment name (…orgFields
) is called a fragment spread. You can use a fragment spread anywhere you use a regular field in any GraphQL operation.
A fragment spread can only be used when the type condition of that fragment matches the type of the object in which you want to use the fragment. There are no generic fragments in GraphQL. Also, when a fragment is defined in a GraphQL document, that fragment must be used somewhere. You cannot send a GraphQL server a document that defines fragments but does not use them.
4.3. Fragments and DRY
Fragments can be used to reduce any duplicated text in a GraphQL document. Consider this example query from the GitHub API.
query MyRepos { viewer { ownedRepos: repositories(affiliations: OWNER, first: 10) { nodes { nameWithOwner description forkCount } } orgsRepos: repositories(affiliations: ORGANIZATION_MEMBER, first: 10) { nodes { nameWithOwner description forkCount } } } }
This query uses a simple alias with field arguments to have two lists with an identical structure: one list for the repositories owned by the current authenticated user and a second list of all the repositories under organizations of which the current authenticated user is a member.
This is a simple query, but there is room for improvement. The fields under a repository connection are repeated. We can use a fragment to define these fields just once and then use that fragment in the two places where the nodes
field is repeated. The nodes
field is defined on the special RepositoryConnection
in GitHub (it is the connection between a user and a list of repositories).
Here is the same GraphQL operation modified to use a fragment to remove the duplicated parts.
query MyRepos { viewer { ownedRepos: repositories(affiliations: OWNER, first: 10) { ...repoInfo } orgsRepos: repositories(affiliations: ORGANIZATION_MEMBER, first: 10) { ...repoInfo } } } fragment repoInfo on RepositoryConnection { nodes { nameWithOwner description forkCount } }
Pretty neat, right? But as I mentioned, the DRY benefit of fragments is less important. The big advantage of fragments is that they can be matched with other units of composition (like UI components). Let’s talk about that!
4.4. Fragments and UI components
The word component can mean different things to different people. In the UI domain, a component can be an abstract input text box or Twitter’s full 280-character tweet form with its buttons and counter display. You can pick any part of an application and call it a component. Components can be small or big. They can be functional on their own, or they can be parts that have to be put together to make something functional.
Bigger components can be composed of smaller ones. For example, Twitter’s TweetForm
component may consist of a TextArea
component with a TweetButton
component and a few others to attach an image, add a location, and count the number of characters typed in the text area.
All HTML elements can be considered simple components. They have properties and behaviors, but they are limited because they cannot represent dynamic data. The story of UI components gets interesting when we make a component represent data. We can do that with modern libraries and frameworks like React.js, Angular.js, and Polymer.js. These data components can then be reused for any data that matches the shape they have been designed to work with. The data components do not care about what that data is; they are only concerned about the shape of that data.
The idea of rich components is comes natively to the browser with what are commonly called web components. Many browsers support most of the features needed to define and use web components. The Polymer.js project is designed to provide polyfills to support using web components in any browser and then to enhance the features offered by these components.
Let’s assume we are building an app like Twitter by using rich data components, and let’s take one example page from that app and analyze it in terms of components and their data requirements. I picked the user’s profile page for this example.
The user’s profile page is simple: it displays public information about a user, some stats, and a list of their tweets. For example, if you navigate to twitter.com/ManningBooks on Twitter, you will see something like figure 3.8.

I can see at least 13 components on this page:
-
The
Header
component, which can include the following components:ProfileImage
,BackgroundImage
,UserInfo
,TweetCount
,FollowingCount
, andFollowersCount
-
The
Sidebar
component, which can include the following components:UserMedia
,MediaItem
, andSuggestedFollowers
-
The
TweetList
component, which is simply a list ofTweet
components
This is just my choice of components. The page can be built with many more components, and it can also be built with just two components. No matter how small or big the components you design are, they share a simple characteristic: they all depend on data that has a certain shape.
For example, the Header
component in this UI needs a data object to represent a profile. The shape of that data object might look like this.
const profileData = { tweetsCount: ·-·-·, profileImageUrl: ·-·-·, backgroundImageUrl: ·-·-·, userName: ·-·-·, handle: ·-·-·, bio: ·-·-·, location: ·-·-·, url: ·-·-·, followingCount: ·-·-·, followersCount: ·-·-·, };
The TweetList
component needs a data object that might look like this.
const tweetList = [ { id: ·-·-·, userName: ·-·-·, userHandle: ·-·-·, date: ·-·-·, body: ·-·-·, repliesCount: ·-·-·, tweetsCount: ·-·-·, likes: ·-·-·, }, { id: ·-·-·, userName: ·-·-·, userHandle: ·-·-·, date: ·-·-·, body: ·-·-·, repliesCount: ·-·-·, tweetsCount: ·-·-·, likesCount: ·-·-·, }, ·-·-·, ];
These components can be used to render information about any profile and any list of tweets. The same TweetList
component can be used on Twitter’s main page, a user’s list page, or the search page.
As long as we feed these components the exact shape of data they need, they will work. This is where GraphQL comes into the picture: we can use it to describe the data shape that an application requires.
A GraphQL query can be used to describe an application data requirement. The data required by an application is the sum of the data required by that application’s individual components, and GraphQL fragments offer a way to split a big query into smaller ones. This makes a GraphQL fragment the perfect match for a component! We can use a GraphQL fragment to represent the data requirements for a single component and then put these fragments together to compose the data requirements for the entire application.
To simplify the Twitter example, we will build the profile page with just these four primary components: Header
, Sidebar
, TweetList
, and Tweet
. Let’s come up with the data required by the Twitter profile page example using a single GraphQL query for each component we identified The data required by the Header
component can be declared using this GraphQL fragment.
Header
UI componentfragment headerData on User { tweetsCount profileImageUrl backgroundImageUrl userName handle bio location url createdAt followingCount followersCount }
The data required by the Sidebar
component can be declared using the following fragment.
Sidebar
UI componentfragment sidebarData on User { SuggestedFollowers { profileImageUrl } media { mediaUrl } }
Note that the SuggestedFollowers
part and the media
part can also come from the subcomponents we identified earlier in the Sidebar
component.
The data required by a single Tweet
component can be declared as follows.
Tweet
UI componentfragment tweetData on Tweet { user { userName handle } createdAt body repliesCount retweetsCount likesCount }
Finally, the data required by the TweetList
component is an array of the exact data required by a single Tweet
component. So, we can use the tweetData
fragment here.
TweetList UI
componentfragment tweetListData on TweetList { tweets: { ...tweetData } }
To come up with the data required by the entire page, all we need to do is put these fragments together and form one GraphQL query using fragment spreads.
query ProfilePageData { user(handle: "ManningBooks") { ...headerData ...sidebarData ...tweetListData } }
Now we can send this single ProfilePageData
query to the GraphQL server and get back all the data needed for all the components on the profile page.
When the data comes back, we can identify which component requested which parts of the response and make those parts available to only the components that requested them. This helps isolate a component from any data it does not need.
But this is not the coolest thing about this approach. By making every component responsible for declaring the data it needed, these components have the power to change their data requirements when necessary without having to depend on any of their parent components in the tree.
For example, let’s assume Twitter decided to show the number of views each tweet has received next to the likesCount
. All we need to do to satisfy this new data requirement is to modify the tweetData
fragment.
fragment tweetData on Tweet { user { userName handle } createdAt body repliesCount retweetsCount likesCount viewsCount }
None of the other application components need to worry about this change or even be aware of it. For example, the direct parent of a Tweet
component, the TweetList
component, does not have to be modified to make this change happen. That component always constructs its own data requirements by using the Tweet
component’s data requirement, no matter what that Tweet
component asked for. This is great. It makes maintaining and extending this app a much easier task.
Fragments are to queries what UI components are to a full application. By matching every UI component in the application to a GraphQL fragment, we give these components the power of independence. Each component can declare its own data requirement using a GraphQL fragment, and we can compose the data required by the full application by putting together these GraphQL fragments.
4.5. Inline fragments for interfaces and unions
Earlier in this chapter, we saw an example of an inline fragment when we talked about the Node interface. Inline fragments are, in a way, similar to anonymous functions that you can use without a name. They are just fragments without names, and you can spread them inline where you define them.
Here is an inline fragment use case from the GitHub API.
query InlineFragmentExample { repository(owner: "facebook", name: "graphql") { ref(qualifiedName: "master") { target { ... on Commit { message } } } } }
Inline fragments can be used as a type condition when querying against an interface or a union. The bolded part in the query in listing 3.34 is an inline fragment on the Commit
type within the target
object interface; so, to understand the value of inline fragments, you first need to understand the concepts of unions and interfaces in GraphQL.
Interfaces and unions are abstract types in GraphQL. An interface defines a list of "shared" fields, and a union defines a list of possible object types. Object types in a GraphQL schema can implement an interface that guarantees that the implementing object type will have the list of fields defined by the implemented interface. Object types defined as unions guarantee that what they return will be one of the possible types of that union.
In the previous example query, the target
field is an interface that represents a Git object. Since a Git object can be a commit, tag, blob, or tree, all these object types in the GitHub API implement the GitObject
interface; because of that, they all get a guarantee that they implement all the fields a GitObject
implements (like repository
, since a Git object belongs to a single repository).
Within a repository, the GitHub API has the option to read information about a Git reference using the ref
field. Every Git reference points to an object, which the GitHub API named target
. Now, since that target can be one of four different object types that implement the GitObject
interface, within a target
field, you can expand the selection set with the interface fields; but you can also conditionally expand its selection set based on the type of that object. If the object that this ref
points to happens to be a Commit
, what information from that commit are you interested in? What if that object is a Tag
?
This is where inline fragments are useful because they basically represent a type condition. The inline fragment in the previous query essentially means this exact condition: if the object pointed to by the reference is a commit, then return the message of that commit. Otherwise, the target will return nothing. You can use another inline fragment to add more cases for the condition.
The union concept is probably a bit easier to understand. It is basically OR
logic. A type can be this or that. In fact, some union types are named xOrY
. In the GitHub API, you can see an example under a repository field, where you can ask for issueOrPullRequest
. Within this union type, the only field you can ask for is the special __typename
, which can be used to answer the question, "Is this an issue or a pull request?"
Here is an example from the facebook/graphql repository.
query RepoUnionExample { repository(owner: "facebook", name: "graphql") { issueOrPullRequest(number: 3) { __typename } } }
The issueOrPullRequest
with number 3 on this repository happens to be an issue. If you try the query with number 5 instead of 3, you should see a pull request. An inline fragment is useful here to conditionally pick fields within an issueOrPullRequest
based on the type. For example, maybe we are interested in the merge information of a pull request and the closing information of an issue. Here is a query to pick these different fields based on the type of the issueOrPullRequest
whose number is 5.
query RepoUnionExampleFull { repository(owner: "facebook", name: "graphql") { issueOrPullRequest(number: 5) { ... on PullRequest { merged mergedAt } ... on Issue { closed closedAt } } } }
Since #5 (in this repository) is a pull request, the merged
and mergedAt
fields will be used for this query.
Another common use of union types is to implement a search field to search among multiple types. For example, a GitHub user search might return a user object or an organization object. Here is a query to search GitHub users for the term "graphql".
query TestSearch { search(first: 100, query: "graphql", type: USER) { nodes { ... on User { name bio } ... on Organization { login description } } } }
You should see users who have the term "graphql" somewhere in their profile and organizations with that term in their name or description. When the matching returned item is a User
object, the fields name
and bio
are returned; and when the item is an Organization
object, the fields login
and description
are returned.
5. Summary
-
You can pass arguments to GraphQL fields when sending requests. GraphQL servers can use these arguments to support features like identifying a single record, limiting the number of records returned by a list field, ordering records and paginating through them, searching and filtering, and providing input values for mutations.
-
You can give any GraphQL field an alias name. This enables you to customize a server response using the client’s request text.
-
You can use GraphQL directives to customize the structure of a GraphQL server response based on conditions in your applications.
-
Directives and field arguments are often used with request variables. These variables make your GraphQL requests reusable with dynamic values without needing to resort to string concatenation.
-
You can use fragments, which are the composition units of GraphQL, to reuse common parts of a GraphQL query and compose a full query by putting together multiple fragments. This is a winning strategy when paired with UI components and their data needs. GraphQL also supports inline fragments that can be used to conditionally pick information out of union object types or object types that implement an interface.