In Progress
Unit 1, Lesson 1
In Progress

Why GraphQL?

For years the state of the art in HTTP API design has been RESTful JSON APIs. But the REST pattern imposes architectural constraints that may be inconsistent with the needs of modern rich web applications. A new protocol has been growing in popularity to address these changing needs. The GraphQL standard seeks to expose APIs that let clients specify exactly what information they need, and in what form.

In today’s episode, guest chef Nat Budin shows us how a traditional RESTful API can lead to an inefficient client/server conversation. Then he demonstrates the GraphQL alternative. Finally, he gives us a quick overview of some of the tools that are available for exposing GraphQL APIs. Enjoy!

Video transcript & code

Why GraphQL?

REST APIs

a slide with three bullets (JavaScript frontend, mobile apps, third-party devs

Just about every web application these days has some kind of API. If you're going to use a rich JavaScript frontend, or support iOS or Android apps, or expose your app functionality to third party developers, you'll need an API.

a forum topic

Suppose we're making a discussion app. We want to be able to show a forum topic, which has some posts in it, and we also want to show some stuff about the user who made each post, like their name and avatar picture.

OK, let's build a REST API for this. We can model the things in our forum as resources.

We'll need an action to get a list of the topics...

an action to get a particular topic...

an action to get a list of the posts in that topic...

an action to get a particular post in that topic...

...and an action for getting a particular user profile.


GET /topics
GET /topics/123
GET /topics/123/posts
GET /topics/123/posts/12
GET /users/987

a forum topic

With that in place, we can use the API to retrieve the data we need to show this page.

HTTP request for the topic

All we have to do is request the topic we want...


GET /topics/123
← { "id": 123, "title": "Nintendo Switch" }

HTTP request for the posts in the topic

And then request the posts in that topic...


GET /topics/123/posts
← [
  {
    "id": 6,
    "content": "I didn't see a dedicated thread and…",
    "user_id": 987
  },
  {
    "id": 7,
    "content": "Super into the system, already have money set aside…",
    "user_id": 1020
  }
]

HTTP requests for each of the users

Oh, and we need to get the data for each of the users who posted in that topic too.


GET /users/987
← { "id": 987, "username": "Farley2k" }

GET /users/1020
← { "id": 1020, "username": "DemonGSides" }

JSON response containing all the data

OK, obviously this isn't great. We're making a whole lot of requests just to show a single view. It's tempting to change the single topic endpoint and have it return all the data we want.


{
  "id": 123,
  "title": "Nintendo Switch",
  "posts": [
    {
      "id": 6,
      "content": "I didn't see a dedicated thread and…",
      "user": { "id": 987, "username": "Farley2k" }
    },
    {
      "id": 7,
      "content": "Super into the system, already have money set aside…",
      "user": { "id": 1020, "username": "DemonGSides" }
    }
  ]
}

But by doing that, we've made some assumptions that might not always be true. We've assumed that the client wants all this data, every time. This is probably true for our particular use case, but it might not be true for every API client someone might write for our forum.

Admin-facing post summary screen

It might not even be true for every page on our own site. For this page, we need a whole different cross-section of the data. You could imagine writing a new endpoint at /admin_posts, or adding a query parameter... either way, it gets redundant and a little messy.

GraphQL APIs

GraphQL logo

GraphQL gives us a different way of solving this problem. GraphQL is, as its creators say, "a query language for your API."

In GraphQL, we might write a query to retrieve our forum posts like this.


query {
  topic(id: 123) {
    title

    posts {
      id
      content
    }
  }
}

If we run that query, we get JSON back. The shape of the JSON response exactly matches the shape of our query.


{
  "data": {
    "topic": {
      "title": "Nintendo Switch",
        "posts": [
          {
            "id": 6,
            "content": "I didn't see a dedicated thread and…"
          },
          {
            "id": 7,
            "content": "Super into the system, already have money set aside…"
          }
        ]
      }
    }
  }
}

But we also want to get the users who made each post. That's no problem!


query {
  topic(id: 123) {
    title

    posts {
      id
      content

      user {
        id
        username
      }
    }
  }
}

Again, we get back exactly what we asked for - no more, no less.


{
  "data": {
    "topic": {
      "title": "Nintendo Switch",
        "posts": [
          {
            "id": 6,
            "content": "I didn't see a dedicated thread and…",
            "user": { "id": 987, "username": "Farley2k" }
          },
          {
            "id": 7,
            "content": "Super into the system, already have money set aside…",
            "user": { "id": 1020, "username": "DemonGSides" }
          }
        ]
      }
    }
  }
}

Every GraphQL server has a schema that tells clients what types of objects they can request, and what they can expect back. Here's the schema for one of the types in our forum app.


type Post {
  id: Int!
  content: String
  user: User!
}

As you can see, each of the fields has a type. Types that end in a "bang" character are guaranteed not to be null. The Int and String types come with GraphQL, but User doesn't. We have to define that in the schema too.


type User {
  id: Int!
  username: String!
}

Let's also define a Topic type. A topic has many posts, so we represent it as an array of Post objects using square brackets.


type Topic {
  id: Int!
  title: String
  posts: [Post]!
}

Finally, all GraphQL APIs need a root query type, which is the fields you can type inside the word "query." In this case, we want to be able to retrieve a topic by ID. In GraphQL, any field can take parameters, just like a function, so here's how we can do that:


type Query {
  topic(id: Int!): Topic!
}

Let's consider what would happen if the API developers added more fields - for example, a post count for each user.


type User {
  id: Int!
  username: String!
  posts_count: Int!
}

When we re-execute this query, we don't see the newly added field, because we didn't explicitly request it. This reinforces the point that with GraphQL, we only get what we ask for.


{
  "data": {
    "topic": {
      "title": "Nintendo Switch",
        "posts": [
          {
            "id": 6,
            "content": "I didn't see a dedicated thread and…",
            "user": { "id": 987, "username": "Farley2k" }
          },
          {
            "id": 7,
            "content": "Super into the system, already have money set aside…",
            "user": { "id": 1020, "username": "DemonGSides" }
          }
        ]
      }
    }
  }
}

The JSON response from before but with post_count highlighted

That's not what would happen in the REST version. There, the posts_count would get added to every response. In the best case, that's some wasted bandwidth sending data the client doesn't care about. If we're not so lucky, some clients might even break because they're receiving data they don't expect.


{
  "data": {
    "topic": {
      "title": "Nintendo Switch",
        "posts": [
          {
            "id": 6,
            "content": "I didn't see a dedicated thread and…",
            "user": { "id": 987, "username": "Farley2k", "posts_count": 58 }
          },
          {
            "id": 7,
            "content": "Super into the system, already have money set aside…",
            "user": { "id": 1020, "username": "DemonGSides", "posts_count": 3 }
          }
        ]
      }
    }
  }
}

The GraphQL ecosystem

The front page of graphql-ruby.org

There are some great tools out there for using GraphQL in Ruby apps. My favorite is the gem named "graphql", written by Robert Mosolgo. It's a library for powering GraphQL web backends.   ( GraphQL gem for Ruby - GraphQL gem for Rails)

Here's how we might implement the User type in our schema using the graphql gem.


class Types::UserType < Types::BaseObject
  field :id, Int, null: false
  field :username, String, null: false
end

Apollo web site

Another tool I like is Apollo Client, which is a GraphQL client for JavaScript. You can use it in React, Vue, Angular, or in plain JavaScript with no framework.

Blank slide

So that's a taste of GraphQL. Happy hacking!

Responses