Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

The Missing GraphQL Introduction

In this introduction to our GraphQL series, I will be going over what GraphQL is, how it works, and the benefits we see over a typical REST api. We’ll jump into code comparisons with standard REST endpoints and try to cover many of the scenarios your daily life as a frontend or backend developer will be dealing with.

Follow this GraphQL Series:

By the end of this article, you’re going to want GraphQL for every backend and frontend you write! Let’s jump in.

What is GraphQL?

GraphQL is a query language where you can describe exactly what you want to get from a system. With a REST request, you have no way of knowing what the server is going to come back with, or the possible fields that could be returned. It also can incorporate WebSocket requests in the same syntax, something that is not possible with REST.

This series assumes you have some frontend and backend experience, and understand how typical REST endpoints behave.

Benefits of GraphQL on the Server

When building a normal REST endpoint, you may find out you need to add or remove fields weeks or months later. When this happens, you have to be very careful, you may have legacy clients using that endpoint. For instance, this REST server may be used by multiple frontend websites, and another backend service.

When using GraphQL, if an endpoint needs changes (ex: adding an email field to a users endpoint), your client side applications can find out almost immediately. This is possible thanks to eslint-plugin-graphql, which compares your client side queries to the server’s schema.

This sort of thing couldn’t be done with a normal REST interface. Although this doesn’t save you from breaking changes with an outside party using your api, it will if your api powers a single frontend application, like a React or Angular single page app. With REST, if an endpoint changed and broke your client code, you will not know until loading the app and clicking around.

When you make the switch to GraphQL, you no longer need to remember the difference between PUT and PATCH. With GraphQL, HTTP request methods don’t matter. Every operation is done over GET or POST. Many GraphQL servers don’t even use GET, everything is a POST.

There are many more server side benefits with GraphQL, including management of websocket connections, and single purpose resolvers. These things will be easier to understand after we get into building out our server in the next post.

Benefits of GraphQL on the Client (browser)

On the client-side, the benefits of GraphQL can be seen even clearer. Normal REST servers can cause a lot of issues for frontend developers, especially if the teams are segregated. Having to manage the local state and querying multiple endpoints for data you need is no longer an issue with GraphQL. In a large SPA (single page app) that I help maintain, we have to build out lots of boilerplate to manage state. If we used GraphQL, it could handle most of the state problems for us, opmtimistic update our interface, handle loading states, and so much more.

These things may seem weird. “What? No more RESTful methods, everything is over a POST?” or, “I have to write out every single field I want? What if I need a lot of data?” I started to have these questions in my mind back when I started implementing GraphQL. I hope by the end of this series, any doubts you have will be answered. After using GraphQL for over two years, I always start new projects with it, and couldn’t imagine a future without it.

Apollo for GraphQL

In this series, I’ll be using Apollo server and client for all of the code examples. This library is a nice abstraction on top of the GraphQL spec. It makes it easy to setup our server, make requests, and handle state management on the client. All code examples will be written in Node / JS.

Code Examples

Now that we have the introduction and some high level information out of the way, I am going to create some examples showing a hypothetical REST endpoint and how a GraphQL server would do the same thing. In the posts following this one, I will have real code and live examples that you can run yourself.

In the remainder of this introduction, we are going to step through the following:

Before we start looking at hypothetical code differences, I want to get a couple pieces of terminology out of the way. A GraphQL server is made up of the following:

This stuff is really straight forward, but we need to quickly go over them before you start seeing these terms everywhere.

A resolver is a function that returns some data. It’s that simple.

A query is a request to the server for some fields or data. Every query has at least one resolver.

Anytime we want to change data on the server, we use a mutation. It’s almost identical to a query, but lets us know some things are going to be modified by our request.

A subscription is almost the same as a query, except it will give us data in real time, over a websocket connection.

Queries, mutations, and subscriptions can accept an input type. Think of this as the arguments or parameters that you are passing to a REST api.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Finally, types are simply descriptions of what your GraphQL server returns. If a users endpoint returns an id and a name. We might call this our User type! Here’s an example:

query {
  user(input: {id: 1}) {
    id
    user
  }
}

This would be a query, with an input type that accepts an id parameter, and returns a User type.

REST vs GraphQL

Before we do this example, I want to mention that I am going to keep this as realistic as possible. I have seen some GraphQL vs REST comparasions that were very naive. For example:

In REST:

let orgUsers = await request('http://test.com/organizationUsers?id=1')

// orgUsers looks like
{
  organization: 'Test',
  userIds:[1,2,3,4]
}
//Now request the users
let users = await request(`http://test.com/usersById?id=${orgUsers.userIds.join(',')}`)

// users looks like
[
  {
    id: 1,
    name: 'John Doe',
    email: '[email protected]'
  }
]

In GraphQL:

gql`
  query {
    orgUsers(id: 1) {
      id
      name
      email
    }
  }
`;

// The response to this query would be

[
  {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
  },
];

As you can see we can describe and make one query using GraphQL, but with a REST api it would take two!

WRONG!

This is the sort of comparisons I dislike. It is incorrect for us to say a REST api HAS to require multiple requests. Any GraphQL query could be built into a single REST endpoint. Although with GraphQL, I would argue that we can accomplish it with less code complexity. Resolving fields and relations is setup in such a way that it is easy to keep adding on to. Let’s take a look at a realistic organization users comparison:

In REST:

// request represents a fake wrapper function around the fetch api
let orgUsers = await request('http://test.com/users?organizationId=1');
  // orgUsers looks like
  {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
  }
];

In GraphQL, it would be the same as our last example:

gql`
  query {
    orgUsers(id: 1) {
      id
      name
      email
    }
  }
`;

// The response to this query would be

[
  {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
  },
];

This doesn’t look much different. Why do we need GraphQL? I think you’ll start to understand when we add on a relationship. A relationship in this case would be a separate database table that has a different piece of data. In this case, statuses that are tied to a user id. Here’s the new comparison:

In REST, we would update our server to send statuses from the same endpoint. Our clients have no idea this new field is there. Also, some people may be loading our user list but not want statuses. Now, everyone will get statuses every time they hit the endpoint, whether they want it or not:

let orgUsers = await request('http://test.com/users?organizationId=1');
  // orgUsers looks like
  {
    id: 1,
    name: 'John Doe',
    statuses: [{ id: 1, text: 'My First Status' }],
  }
];

Of course, it’s completely possible to do something like this:

let orgUsers = await request(
  'http://test.com/users?organizationId=1&include=statuses',
);

If our server had an include parameter, it could list out some optional fields to load. The problem with this is twofold. It’s a manual process to add certain things to be includable. You can’t easily make all fields optional, and if we did that, we would essentially be trying to build a lesser version of GraphQL.

Statuses query in GraphQL:

gql`
  query {
    orgUsers(id: 1) {
      id
      name
      email

      statuses {
        id
        text
      }
    }
  }
`;

// The response to this query would be

[
  {
    id: 1,
    name: 'John Doe',
    statuses: [{ id: 1, text: 'My First Status' }],
  },
];

Are you starting to see the benefit here? If not, you may be wondering why we need to define every field we want. It seems tiresome right? A big endpoint could mean lots of fields and too much work to write all of those out. Why can’t we just get whatever the server wants to send us like REST?

The best reason is caching. You will see when we implement our application, that so much state management is handled for us because of this built in type / querying system.

Second, GraphQL makes it easy to split out your user interface, so that you don’t have gigantic queries.

Lastly, even if you did have a big query that needed to be ran, there is a feature called fragments that will let you easily request big chunks of data and you’ll only have define the query one time. More on that further in the series.

Back to the comparison… If we have a list of users on our about page, we may not want their statuses. With GraphQL, we can do another query:

gql`
  query {
    orgUsers(id: 1) {
      id
      name
    }
  }
`;

Our server will now only return the name and user id which we can use to create a list of names for our about page. If we were only using REST, we would be loading a lot of extra data, and potentially more database queries for things like statuses that we may never need.

Pagination with GraphQL

Now that you can start to feel the power GraphQL gives us, lets do another example with pagination.

// imagine our REST request function can take an object
// that will be turned into JSON and sent to the server

let orgUsers = await request('http://test.com/users', {
  organizationId: 1,
  page: 2,
});

In GraphQL:

gql`
  query {
    orgUsers(input: {organizationId: 1, page: 2}) {
      users {
        id
        name
        email

        statuses {
          id
          text
        }
      }

      pagination {
        total
        currentPage
      }
    }
  }
`;

// The response to either way would be:

 // orgUsers looks like
{
  pagination: {total: 50, currentPage: 2},
  users: [
    {
      id: 1,
      name: 'John Doe',
      statuses: [{ id: 1, text: 'My First Status' }],
    }
  ]
}

The first thing I want to point out, is that we had to change our structure. Because we need pagination info, we have to return an object, with pagination and users instead of an array on the root of the response:

// Before, this would give us the first users name
response.data[0].name;
// After, it is nested
response.data.users[0].name;

GraphQL is a versionless api setup, which means you really don’t want to end up with backwards incompatible changes like the one we just made. Existing people using our service will have broken code if we pushed this up.

For this reason, a best practice is to always nest your resolvers, even when you may not need anything extra at that point in time. Here’s an example one more time. We should have made our very first example above like this:

From:

gql`
  query {
    orgUsers(id: 1) {
      id
      name
      email
    }
  }
`;

to

gql`
  query {
    orgUsers(id: 1) {
      users {
        id
        name
        email
      }
    }
  }
`;

I will mention sometimes I do not follow this pattern, even though I should. When you are building an api for a single client, you can get away with it, because you are generally updating the server and the client before going to production.

Next thing to note, is that we are now using an input type:

orgUsers(input: {organizationId: 1, page: 2})

Any time a query or mutation may need more than one argument passed to it, always create an input type. Think of an input type as one variable, like an object, that can take a bunch of values inside it. This is much nicer for the client to use, compared to this:

orgUsers(organizationId: 1, page: 2)

The reason for this, is because each one of those root variables need to be defined on the client side, wheras an input type can change or add fields, but client code doesn’t always have to change. This will become more obvious in our next post with real client side code.

Now that we talked a bit about the GraphQL changes needed for this pagination feature, let’s discuss the REST side. How do we know what the REST endpoint allows us to pass? Currently, it has an organizationId parameter, and now a page parameter. With our GraphQL query, there is built in documentation, and built in validation that we are passing the required fields.

With REST, you can use something like Swagger which lets you make a spec of what an endpoint takes and can return. But it is not as in depth as what we get with GraphQL. You have to define what is returned or updated, and update your code. Whereas with GraphQL, defining types, required fields, is your code. In the next post, you will see how powerful the built in documentation is that we get out of the box with Apollo Server. It blows any REST tools out of the water from my experience.

Subscriptions

Here’s the final comparison. Live updating with websockets. Generally, this code is a lot different than your REST request code in an application. This is because of a REST limitation, it has no way to let you keep a connection open for real time events. With GraphQL however, we can represent this real time data in the same way that we do with queries and mutations:

let statuses = request('/statuses')
// gives us a statuses array
[
  {
    text: 'First Status!',
  }
];
//later somewhere in our code, after setting up a websocket connection, we listen for events

socket.on('newStatus', status => statuses.push(status));
//render something to the screen

This is some basic psuedo code representing a normal GET request for a list of statuses, and then using some sort of web socket library that calls a function any time the server sends newStatus down to us. Not only is this a lot different than our REST code on the client, the server side may have to handle it different as well. With GraphQL we can keep it almost the same as our normal query and mutation code! Here’s what a request would look like in GraphQL:

query {
  statuses {
    text
  }
}

subscription {
  newStatus {
    text
  }
}

We can then tell Apollo client to add the newStatuses onto our statuses query. Pretty cool!

Conclusion

I hope you have a good taste for GraphQL at this point. Here’s one more thing to think about before jumping in to the next post. Those of you experienced with SQL may be wondering, GraphQL does not handle database relationships for us. Say we used that statuses example earlier to load 50 users and their statuses.

(code from above)

gql`
  query orgUsers(id: 1) {
    id
    name
    email

    statuses {
      id
      text
    }
  }
`;

// The response to this query would be

[
  {
    id: 1,
    name: 'John Doe',
    statuses: [{ id: 1, text: 'My First Status' }],
  },
  //50 more users and statuses
];

We would be doing 1 query for the 50 users, and then 50 queries for the statuses. We really want two queries to be ran. 1 for the 50 users, and then 1 for all the statuses. Due to the nature of resolving in GraphQL, it’s not possible to make one query with a join. This type of thing scares people that are new to the paradigm, but I will be showing you how to recognize and fix this sort of thing, and also why GraphQL works the way it does in my next post on GraphQL resolvers.

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover