How to Write GraphQL Queries
GraphQL is often touted for its efficiency when consuming data from APIs. There are two key aspects to its efficiency:
- You get only the data you need. GraphQL will only respond with the fields you specify, meaning it does not need to send unnecessary data across the wire, potentially saving your application - and your users - significant amounts of data over the course of numerous transactions.
- You get all the data you need in a single request. Rather than spread different parts of the data across multiple endpoints, GraphQL lets you get all the data you require from a single request. For example, I can get user information and their order information in a single query rather than hitting two separate endpoints.
Both of these benefits are made possible by the way GraphQL lets you construct a query, but learning how to write a GraphQL query can take some practice. In this article, we'll look at some of the basics of constructing a query in GraphQL to help you get started. In this post, we'll focus on retrieving data from a GraphQL API (as opposed to modifying, or mutating, the data).
Getting Set Up
More and more companies are adopting GraphQL for their APIs. GitHub was a early adopter of the technology. The GraphQL spec was open sourced in 2015 and the GitHub GraphQL API was announced back in 2016. Pretty much anything you can do in GitHub, you can do via their GraphQL API. Today we are going to use it to retrieve user and repository information in order to explore how you write GraphQL queries.
There are two ways to explore the API:
- Use their API Explorer. If you log in to your GitHub account, it will allow you to explore the API schema and construct and run queries via a GraphiQL interface.
- The alternative is to use a desktop API explorer. If you're a REST API consumer, you may be comfortable using something like Postman, which, you'll be pleased to know, does support GraphQL. In this post, I'll be using the free, GraphQL Playground, which is an Electron desktop app that can be installed via Homebrew or by downloading the latest release.
In order to use the API outside the API Explorer, you'll need to generate a Personal Access Token. If you choose to use GraphQL Playground, you'll first need to add the API endpoint of https://api.github.com/graphql
. If you try to run anything, you'll get a 401 error though. To fix this, add an authorization header in the HTTP headers (located on the bottom left side of the application window):
{
"Authorization":"Bearer MY_TOKEN"
}
Once the HTTP headers are set up, you should be able to click on the Docs tab on the far right to explore the types and queries available within the GitHub API.
Ok. We're finally ready to start building a query.
Creating Your First Query
Let's start with a relatively simple query to find out how many people are following the topic of GraphQL on GitHub using the topic
query. In a RESTful API situation, this might involve calling an endpoint such as /v3/search/topics
which would return all the details we need. However, every GraphQL query calls the same endpoint, so you must define what you are querying in the request. Luckily, GraphQL IDEs like GraphiQL or the GraphQL Playground will offer code hinting.
Let's start by calling the topic
query:
{
topic
}
You may notice that your GraphQL IDE indicates that there is an error in your request.
As the error indicates, the topic
query expects a name
argument of type string. So let's add our first argument, which is placed in parentheses after the query:
{
topic(name: "graphql")
}
You'll note that we're still seeing an error. Why? Well, the way that GraphQL is able to supply you only the data you need is by requiring that you specify the fields you require within your query. There is no equivalent of select *
in GraphQL, and this is for a number of good reasons. So, lets specify some fields within our query.
The topic
query has 6 fields available, but really we're only interested in the number of people following it, or the stargazersCount
field:
{
topic(name: "graphql") {
stargazerCount
}
}
Finally, no errors. Let's run our query. Here's the response:
{
"data": {
"topic": {
"stargazerCount": 12201
}
}
}
You'll notice that the response matches the query, meaning we don't need to waste time figuring out the structure of the response (something I have spent inordinate amounts of time doing in the past).
Arguments
In the above example, we had a single argument that was a string. Arguments can be any of the GraphQL basic types but they can also be an enum or even a custom object type. For example, GitHub's search
query takes a searchType
argument that is an enum with the possible values of ISSUE
,REPOSITORY
or USER
.
{
search(query: "GraphQL", type: REPOSITORY) {
repositoryCount
}
}
Meanwhile, for example, the repositories
object within a user
query takes a custom object argument of type RespositoryOrder
that specifies both the field to order by and the direction in which to order. Here's a snippet from the larger query we'll look at later in this article:
...
repositories(orderBy: {field:UPDATED_AT, direction: DESC}) {
...
The point here is that GraphQL allows APIs to support very complex querying capabilities in arguments.
Nested Objects
So far, we have a very simple query that gets only a single field. Let's make this a little bit more complex by adding a nested object to get multiple sets of data in a single query. In this case, let's get a list of related topics to GraphQL within GitHub using the related topics object.
{
topic(name:"graphql") {
stargazerCount
relatedTopics {
name
stargazerCount
}
}
}
You'll see that we need to specify the object and then again the fields within that object that we want to retrieve. In this case, relatedTopics
did not require an argument. However, if you look at the docs, you'll see that it does have an optional argument of first
that defaults to 3, meaning it will only send the first 3 results. Let's define that to give us 10 (the maximum the API allows):
{
topic(name:"graphql") {
stargazerCount
relatedTopics(first: 10) {
name
stargazerCount
}
}
}
As you can see, you can specify arguments not just on the primary query but on nested object queries as defined by the schema.
Aliases
Now let's imagine that we want to get the stargazer count for both GraphQL and JavaScript in a single request. You might try something like this:
{
topic(name: "graphql") {
stargazerCount
}
topic(name: "javascript") {
stargazerCount
}
}
That would give you an error however because we have defined conflicting queries in a single request. To solve this, we need to use an alias on each of the queries:
{
graphql:topic(name: "graphql") {
stargazerCount
}
javascript:topic(name: "javascript") {
stargazerCount
}
}
This will return a response containing the aliases you specified.
{
"data": {
"graphql": {
"stargazerCount": 12203
},
"javascript": {
"stargazerCount": 73700
}
}
}
It's worth noting that the alias can be any string you want, though it cannot start with a number or contain any special characters other than an underscore (though it can start with an underscore).
Fragments
Let's imagine that we wanted to make this query get more than just the stargazerCount
. We wouldn't want to have to repeat this code for each aliased query. This is where fragments come in. They can represent reusable pieces of query code. For example, instead of writing this:
{
graphql:topic(name:"graphql") {
stargazerCount
relatedTopics(first: 10) {
name
stargazerCount
}
}
javascript:topic(name:"javascript") {
stargazerCount
relatedTopics(first: 10) {
name
stargazerCount
}
}
}
...we can write this:
{
graphql:topic(name:"graphql") {
...topicDetails
}
javascript:topic(name:"javascript") {
...topicDetails
}
}
fragment topicDetails on Topic {
stargazerCount
relatedTopics(first: 10) {
name
stargazerCount
}
}
We define the fragment and give it a name (topicDetails
in this case). You must define the type it is a fragment on (Topic
in this case) for validation purposes. Then we can reuse the fragment by referencing it's name prefixed by three dots (i.e. ...topicDetails
in this case) wherever we need to reuse it. While we didn't save a ton of code in this example, this can be particularly useful when you find yourself repeating large chunks of code in a complex query.
Variables
The last topic I want to discuss here is variables. Let's imagine I wanted to create a query that gets the user profile information and recent repositories for a user. This wouldn't work if I had to pass a hardcoded user string. Instead, GraphQL allows me to define a variable and then pass in a variables object to the query to populate the variables.
First, let's look at our query. This query basically gives us everything we might want to know to construct a profile page for a GitHub user (perhaps for a developer portfolio home page or something):
query GitHubUserProfile($username: String!) {
user(login:$username) {
name
company
bio
twitterUsername
websiteUrl
url
repositories(first: 10, privacy: PUBLIC, orderBy: {field:UPDATED_AT, direction: DESC}) {
totalCount
edges {
node {
name
description
url
}
}
}
}
}
Not related to variables, but up until now we have been using an anonymous operation (i.e. wrapping the query in curly braces), but best practice says we should define the operation type and name. In this case, our operation type is a query
and the name is GitHubUserProfile
. There are other operation types such as a mutation, which modifies data on the server, and a subscription, which subscribes to updates to changes from the server.
You'll notice I have defined a variable $username
(all variables must be prefixed by a $
) of type string. The exclamation point (!
) indicates that this variable is a required argument. Within the user
query we now reference the $username
variable as the value for the login
argument.
To specify that argument, we need to pass in a JSON object containing a value for username
. We can do that in GraphQL Playground via the variables tab in the bottom left of the application.
In this case, I've supplied my own GitHub username:
{
"username": "remotesynth"
}
...and you can see the result of running the query on the right (I won't duplicate the result here as it is rather long).
Where To Go Next
This isn't a complete guide to queries. There are things like directives, for instance, that I have not touched on here. If you are looking for more details, I recommend the following resources:
- Queries and Mutations on graphql.org
- Understanding Queries in GraphQL by Peter Ekene
- GraphQL Core Concepts on howtographql.com/
Want to know why StepZen chose to use GraphQL? Check out Why GraphQL? by StepZen's CEO, Anant Jhingran.
This Is Awesome, But My Backend Doesn't Support GraphQL
I think GraphQL offers a ton of power for queries, but you may be connecting to first-or-third-party RESTful APIs that haven't yet made the move or to a database backend that doesn't even have an API yet. If so, StepZen can help. We're building a way for you to enable all the backends you connect to to be accessible via a unified GraphQL API. You can learn more and sign up to get access to our private beta at stepzen.com.