You are viewing the preview version of this book
Click here for the full version.

GraphQL vs REST

GraphQL is an alternative approach to provide an API, so it's natural to compare it with REST. In this chapter we'll look into the same data model and how we can represent that in the two ways. Then we'll discuss the benefits of using GraphQL.

What is REST

REST is based on HTTP paths and verbs to provide operations for resources. For example, let's consider a simple data model where we have users and users can have todo items. A simple API could offer retrieving, creating, and listing objects:

  • GET /user => List users
  • GET /user/:id => Get user by ID
  • POST /user => Create user
  • GET /user/:id/todo => List todos for user
  • POST /user/:id/todo => Create todo for user

To get the list of users, the GET /user can return this response:

{
  "users": [
    {
      "id": "user1",
      "name": "Bob"
    },
    {
      "id": "user2",
      "name": "Joe"
    }
  ]
}

Then to get the todos for the first user, a GET /user/user1/todo can return this:

{
  "todos": [
    {
      "name": "todo1",
      "checked": false
    },
    {
      "name": "todo2",
      "checked": true
    }
  ]
}
Note

In this comparison we'll focus on queries, as this is where GraphQL provides more benefits. Modifications are way simpler and GraphQL adds almost nothing there as you'll see in the Mutations chapter.

Following the REST approach gives a good idea what is possible with the API just by looking at the verb + path pairs. It's easy to see what objects are available just by seeing their names in the paths, and the hierarchy among them. In the above example, todo items are part of users, which is evident from the /user/:id/todo path.

On the other hand, nothing enforces a REST-like API to follow these conventions, it's entirely up to the developer (or the designer of the API) to make it easy for others to understand the structure. Also, there is no built-in way to document a REST API. To do that, you'll need to rely on other projects, for example, OpenAPI.

Queries in REST are HTTP requests to the API endpoint. Usually, a query returns one object (/user/:id) or a list (/user) and any referenced objects need a separate request (/user/:id, then /user/:id/todo).

What is GraphQL

GraphQL is based on graphs. There are nodes, that are similar to objects in REST, and edges between them. In our previous example we had users and todos, and a connection between a user and a todo is that a todo belongs to a user.

Users and todos in a graph

In the above data model, the user objects have a todos field and the todo items are referenced through that field. You can think of it as a label on the edge that distinguishes how the objects are connected. For example, let's say users can have friends and blocked users. In that case, two users can be connected in two ways: friend or blocked.

This is visualized like this:

User1 is friends with user2 and blocks user3

Also, edges are directed. A user might block another user but that might not be mutual.

Note

GraphQL comes from Facebook, and you can see how it naturally translates to a social network. People have friends (a connection between the two user objects), they can publish posts (a connection between a user and a post), can comment (a connection between a post and a comment, and also between a user and the comment), and so on. Graph-based APIs work best where queries need related data to an object. For example, getting the todo items is easy to define as a graph operation (just follow the edges from the user). But ultimately, everything can be implemented in GraphQL.

Queries in GraphQL define the whole structure the requester needs. For example, a single query might get a user object and all todos for that user. Or, in a more complex example, a query might get a user, its latest posts, its friends, and their latest posts too.

A single query can retrieve multiple objects
Note

GraphQL needs a schema that defines the object graph and what connections are available from each type. We'll discuss this in detail in the Schema chapter.

Getting back to our original data model of users and todos, a query can ask for a specific user and its todo items:

query {
  user(id: "user1") {
    name
    todos {
      name
      checked
    }
  }
}

A response contains all the fields:

{
  "data": {
    "user": {
      "name": "Bob",
      "todos": [
        {
          "name": "todo1",
          "checked": false
        },
        {
          "name": "todo2",
          "checked": true
        }
      ]
    }
  }
}

The path to GraphQL

An excellent article from PayPal titled GraphQL: A Success Story for PayPal Checkout illustrates the steps teams usually takes to move from REST-like APIs to GraphQL and what the motivations are.

In this chapter we'll take a look into how they progressed from a REST-like API to GraphQL.

Step 1: REST-like

A new API is usually created with purity in mind, and REST is a great choice for that. There is an endpoint for each object, allowing CRUD (Create, Retrieve, Update, Delete) operations for them. For a backend developer, this is the easiest, as these objects usually directly map to the database. Then the frontend(s) can get what it needs by fetching the objects.

REST-based API

This is great from an architectural perspective, but terrible for user-experience. The number of requests and roundtrips the client needs to do to show the page to the user is the #1 contributor to slowness. And REST-like APIs, especially those that follow data normalization best practices, require several requests in series to fetch all data.

The number of roundtrips make clients slow

In the above example, showing a simple overview of the order required 4 roundtrips: order -> product -> manufacturer -> address. This structure follows all the best practices for database architecture and the API is a pure representation of what is stored, but the UX is awful.

Step 2: Orchestration endpoint

The logical next step is to create an endpoint to aggregate the data, so the client needs to send only one request, then the backend fetches the data from the REST API locally. Since the roundtrips are shorter (the REST API is local to this endpoint) this way, the lag the client experiences is just one request.

Orchestration endpoint

This solves the roundtrips problem, but introduces a different one: overfetching. What if a client does not need to show the address of the manufacturer? For example, a mobile app might hide the details by default due to the smaller screen. If the endpoint returns everything any of the clients needs then most of the data will be discarded.

A bad solution is to provide a separate orchestration endpoint for every client. Instead of a single /landing-page, there could be a /landing-page-mobile and a /landing-page-desktop, each client calling the appropriate one. In practice, this leads to every person working on a client having a separate endpoint.

Step 3: Dynamic orchestration

The problem with an orchestration endpoint we used in the previous step is that it's static by nature. One can return all the data for a landing page or a checkout form, but not both. Instead of the client defining what it needs, the server provides data only for some cases.

The logical next step is to make a dynamic orchestration endpoint where the clients can define what they need and the same request can respond to all use-cases. This sounds great at first: the REST-like API keeps its one object-one endpoint structure, then there is one general-purpose orchestration layer on top of that, and clients can enjoy the 1-roundtrip performance with just the data they need.

Dynamic orchestration endpoint aggregating requests to the REST API

There is more, but you've reached the end of this preview
Read this and all other chapters in full and get lifetime access to:
  • all future updates
  • full web-based access
  • PDF and Epub versions