DEV Community

loading...
Cover image for How to Write GraphQL Queries

How to Write GraphQL Queries

Brian Rinaldi
Brian Rinaldi is a Developer Experience Engineer at LaunchDarkly. He's spent over 20 years as a web developer and his current passions are the Jamstack and GraphQL.
Originally published at stepzen.com ・8 min read

GraphQL is often touted for its efficiency when consuming data from APIs. There are two key aspects to its efficiency:

  1. 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.
  2. 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 constuct 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 constucting a query in GraphQL to help you get started. In this post, we'll focus on retreiving 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 retreive 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"
}
Enter fullscreen mode Exit fullscreen mode

HTTP Headers in Playground

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.

Playground Schema docs

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
}
Enter fullscreen mode Exit fullscreen mode

You may notice that your GraphQL IDE indicates that there is an error in your request.

GraphQL query error

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")
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, no errors. Let's run our query. Here's the response:

{
  "data": {
    "topic": {
      "stargazerCount": 12201
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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}) {
...
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 arguement. 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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

This will return a response containing the aliases you specified.

{
  "data": {
    "graphql": {
      "stargazerCount": 12203
    },
    "javascript": {
      "stargazerCount": 73700
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

...we can write this:

{
  graphql:topic(name:"graphql") {
    ...topicDetails
  }
  javascript:topic(name:"javascript") {
    ...topicDetails
  }
}

fragment topicDetails on Topic {
    stargazerCount
    relatedTopics(first: 10) {
      name
      stargazerCount
    }
}
Enter fullscreen mode Exit fullscreen mode

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
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

GraphQL variables

In this case, I've supplied my own GitHub username:

{
  "username": "remotesynth"
}
Enter fullscreen mode Exit fullscreen mode

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

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.

Discussion (0)