5 min read

Why your next API should be GraphQL instead of REST

RESTful APIs were all the rage a few years ago, but GraphQL now has better tooling and developer experience.
A spider web with droplets depicting GraphQL
Photo by George Rosema / Unsplash

A few years ago, I wrote all my APIs as RESTful APIs. I knew GraphQL was a thing but hadn't spent much time learning it. Early exploration showed that it was not a magic API bullet. You still had to write all the logic, and what was this weird schema file I needed? Why bother with the extra layer of complexity?

A RESTful API was easy and required no extra schema. Each resource had simple endpoints for managing it. The code for the API endpoints was simple CRUD most of the time.

This past year at work, we switched from a hacked-together RPC API to a proper GraphQL one. This was much better for our application since the RPC interface was just a poor implementation of GraphQL anyways. The application also heavily relied on relationships, so GraphQL made a lot of sense.

I was skeptical but interested to see how it would go.

Turns out, the tooling these days is excellent. And the benefits proved enormous.

One year later, I'm hooked. When my friend started a side project, my heart sank when I thought about needing to interface with a bunch of REST APIs again. GraphQL was such a better developer experience.

I've been converted.

So why is GraphQL so amazing?

The code is mostly the same, anyways.

When it comes down to writing the code to handle an incoming request, the actual body of the handler is almost identical.

For example, some pseudocode for fetching a resource given an ID:

func resolver(parent, args, context): Resource {
  const id = args.id
  const resource = db.Resource.FindOne({id: id})
  return resource
}
Fetch a resource by ID (GraphQL)

Is that for GraphQL or a REST endpoint?

The code is almost identical.

The main difference is just the function signature. The same is true for fetching a collection of resources, updating, and deleting.

For both cases, you pass arguments to functions and handle the request. There are some minor syntax changes, but the bulk of the logic remains the same.

However, there is a compelling benefit of GraphQL. You get relationships for free.

Let's say you have a schema with two resources: an Author that has many Posts. You only get posts in the context of the author, so your REST API looks like this:

/authors/
/authors/:author_id/
/authors/:author_id/posts
/authors/:author_id/posts/:post_id

You implement four different APIs to fetch the various data at each level.

GraphQL allows you to do this much more simpler:

type Query {
  authors(id: Int): [Author!]!
}

type Author {
  posts(id: Int): [Post!]!
}

There are other ways to approach this, but this schema setup allows you to:

  1. Fetch all authors by not passing an ID (it's optional)
  2. Fetch an author by the specific ID
  3. Fetch all posts for all authors
  4. Fetch all posts for a single author
  5. Fetch only a given post for a given author

Personally, my root queries will often split author and authors to avoid returning an array for a single resource. By placing the Post under the Author resource you write the Post resolver in the parent context of an Author. This simple nesting lets your user query the author and their posts together.

The tooling has gotten very good.

GraphQL is a strongly typed schema allowing it to generate code for client and server libraries.

Therefore, your client can fetch data with perfect type information. Type safety is a massive benefit over hand-crafted REST APIs. Some tools generate types for rest APIs, such as Swagger/OpenAPI. However, those tools are not built into the spec, so you don't automatically get them.

GraphQL has comments built into the schema and an introspection API, which allows it to be self-documenting. Well-written comments forgo the need to have documentation that is maintained separately.

Similarly, because the schema is strongly typed, you don't really need to build custom client libraries. Often services will provide a client lib to use that offers types and methods that are simple to access. GraphQL has that built-in; you just need to bring a client.

You also get the types built for the server resolvers. Some languages, like Go, will generate your entire resolver function. All you need to do is fill in the contents. Others, such as TypeScript, will generate all the types, and you can use them in your resolvers for type safety.

The types hide sensitive information.

You define the types of data in the GraphQL schema. This gives you an easy place to strip out sensitive information. For example, if you have a User type, and that maps to one in your database; you might be saving personal information on the object, like an email or hashed password.

Without proper tooling, if you just do the following:

func handler(request, response) {
  const user = db.User.findCurrent()
  // This will return email and password!
  return user
}

It will return all fields on the user. You need to strip out the sensitive information.

func handler(request, response) {
  const user = db.User.findCurrent()
  delete user.email
  delete user.password
  return user
}

GraphQL will do this automatically by requiring a specific type (in langs like Go), or in TypeScript, it will only expose the fields defined in the schema. This lets you return objects to the GraphQL resolver and have them safely only expose the fields in the schema.

Insomnia and other tools to quickly validate your queries.

Testing GraphQL has also gotten easier, with great support built into tools. My current favorite is Insomnia for testing GraphQL queries outside of my app. Insomnia will fetch the schema and provide autocomplete for making the queries, with support for variables as input. In addition, you can export the project and include it in your source code for a quick way for people to explore and onboard.

There are also other great tools like Apollo's Playground.

REST API purity breaks down quickly.

Over time I've noticed a peculiar downside to REST; not every action falls nicely into CRUD resource management. There is a class of actions that can be put into this format but might not make sense:

  • Creating multiple records at once
  • Updating multiple records at once
  • Starting a long-running job
  • Canceling jobs

While I'm sure you can come up with REST APIs that make sense (starting a job is obviously POST /job, it just returns an HTTP 202 instead of 200 !), you also can argue over others. For example, is canceling a job deleting it, or changing it?

Is bulk update a resource, or just manipulating many others?

REST doesn't have good semantic meanings for these actions. But GraphQL mutations can be named anything so that you can have:

mutation cancelJob(id: Int!): Job

And it doesn't matter if it were a PUT or DELETE. The job is canceled—the flexibility results in a more expressive API.

A single request for page loads

When requesting data for a page (app, site, whatever), RESTful APIs will return their resource, and that's it. Often if you want to fetch related resources, you will need to fetch X and then X's Y.

Some REST APIs let you fetch related resources, which is nice. But you can't fetch unrelated resources such as X and Z. But you can with GraphQL; you can have multiple root queries to fetch data.

When using GraphQL, you only need to make a single HTTP request for all your data.

Now, things can still go disastrously wrong with that request if you ask for too much data. But in most cases, the server can efficiently cache data and return lots of info in a single request.

GraphQL > REST

GraphQL is a powerful query language that has grown up over the past several years. It has incredible tooling which allows you to focus on business logic, defining only the schema to unlock the power. In addition, you have much more flexibility in defining the API, which gives you more control.

REST was vastly better than the APIs that came before it. It was a sane way to think about data and create APIs in a very sensible manner.

But GraphQL is more powerful, easier to use, and is a better developer experience. Your next API should be GraphQL.


I'm building a suite of internet products to find a better alternative to the next tech job. The first product is a SaaS app that helps manage localizations and copy for your app.

If you want to follow along, please find me on Twitter. It means a lot!