What is GraphQL? Better APIs by design

Learn how Facebook’s open standard for querying data can provide a better way to build APIs than REST and Swagger

What is GraphQL? Better APIs by design
Thinkstock

When most of us think of web APIs, we think of REST (Representational State Transfer). You send a request to a request-specific URL, and you receive the results as HTML, XML, JSON, plain text, PDF, JPEG... whatever format makes sense for the application. 

Facebook’s web API system, GraphQL, provides a new way to define APIs. Developers use a strongly typed query language to define both the requests and the responses, allowing an application to specify exactly what data it needs from an API. Thus GraphQL is meant to provide a more efficient, structured, and systematic alternative to REST.

In this article we’ll lay out how GraphQL is different from REST, how those differences impact API design, and why GraphQL often makes a better choice than REST for fetching data from a server.

GraphQL vs. REST

With REST, you typically submit your request by way of a specially crafted URL, with each variety of request sent to a different endpoint—for instance, /movie/2120 vs. /director/5130.

With GraphQL, you submit a declarative request in a JSON-like query for the data you’re seeking, and all requests go to the same endpoint. The schema used for the request determines what data you’ll get back. It’s a standardized, self-describing way to ask for the specific data needed, and only the data needed. 

Using different schemas for different types of requests, instead of using a different endpoint URL format, makes for a much more flexible query mechanism. Although many REST APIs also conform to a common specification, Swagger, there’s no rule that says a REST API has to be generated by Swagger. GraphQL provides a formal definition for the API by default.

In this respect, GraphQL is a little like SQL (Structure Query Language). With an SQL-powered data source, you connect to one common endpoint for all your data requests, and the formatting of the request determines what records you’ll get back. And while there are many different implementations of SQL, the syntax of SQL queries remain highly consistent across those implementations.

GraphQL queries

GraphQL uses a schema, or a data definition, to describe how the data to be retrieved is organized in the query and the response. Anyone who has worked with an ORM (object-relational mapper) should find GraphQL’s data schema definitions familiar:

type Movie {
    id: ID
    title: String
    released: Date
    director: Director
}

type Director {
    id: ID
    name: String
    movies: [Movie]
}

You’ll notice that each element in the schema has a type definition. GraphQL has its own type system for queries, which is used to validate the incoming schema and to return data in a format consistent with the definition.

Queries submitted to GraphQL are similarly defined by a schema:

type Query {
    movie(id: ID!): Movie
    director(id: ID): Director
}

Here we have a query that takes in up to two parameters, movie (by way of its ID number), and director (also by way of an ID.)

The ! next to a type indicates that this is a mandatory element in the query. In other words, you must query for a movie by ID, with the director being optional. Mandatory elements can also be used in a data schema.

Here’s one possible way to format a query defined with the above schema:

query GetMovieByID ($id: ID!) {
    movie(id: $id) {
        name
    }
}

This query uses a separately defined variable ($id), which is required, to look up a movie by its ID number and return its name. Note that GraphQL queries can return related objects and their fields, not just individual fields, by nesting arrays of items inside fields.

Variables for queries like this are passed along in a separate section of the query, using a format like this:

{
    “id”: 23
}

GraphQL types

GraphQL’s query type system specifies many common scalar types, like strings and integers. Most queries will revolve around those. But the type system also includes several advanced types for more sophisticated query work:

  • The interface type can be used to create an abstract type with a pre-defined set of fields, which other types can implement and re-use.
  • The union type allows different kinds of results to be returned across multiple types from a single kind of query.
  • input types can be used to pass whole objects of the above kind as parameters in a query, provided those objects are created out of common scalar types that can be validated.

If you’re working with interface or union objects, you’ll need to use inline fragments and directives to return data based on the conditions those object types can specify.

Another kind of type that can be returned in a query is the edge type, returned in an optional edges field. Edges contain nodes—essentially, data records—and cursors, which are encoded strings that provide contextual information about how to paginate backwards or forwards from that object.

{
    movie {
        name
        actors (first:5) {
            edges {
                cursor
                node {
                    name
                }
            }
        }
    }
}

In this example, a movie node would return the name of the movie and its actors. For each actor in the movie, we’d receive a node containing the actor’s name and a cursor that allows browsing the actor’s “neighbors.”

GraphQL pagination

A common scenario when working with any data source is to request data in pages by way of a cursor. GraphQL provides a number of ways to do pagination.

When you request records, you can specify not only how many records to request and the starting offset, but how to request successive pages. The example code in the previous section returns only the first five actors associated with a given movie—as indicated by the first:5 parameter in parentheses.

The first: clause after actors can be followed by other keywords that describe how to fetch the succeeding items. offset: can be used for simple offsets, but the offset might be thrown off when data is added or deleted.

For the most robust pagination, you’ll want to use a cursor that can be delivered along with the object you’re requesting, by using the edge type described above. This allows you to create pagination mechanisms that are not disrupted when data is inserted or deleted between paginations—for instance, by using the object’s unique ID as a starting key index for other calculations.

GraphQL mutations

With a REST API, you make changes to the server-side data by submitting requests that use POST, PATCH, and other HTTP verbs. With GraphQL, you use a specific query schema to make changes, a mutation query—again, in much the same way SQL uses UPDATE or DELETE queries.

To make changes to the data, you submit a GraphQL query using a schema called a mutation schema:

mutation CreateMovie ($title: String!, $released: Date!) {
    createMovie (title: $title, released: $released){
        id
        title
        released
    }
}

[submitted data]

{
    “title”: “Seven Samurai”
    “released”: “1950”
}

All queries, including mutation queries, can return data. Here, the list of fields in the curly braces after createMovie specifies what we want to see returned from the server after a new record is created with this request. The value for the id field, in this case, would be created by the database; the values for the other fields are submitted in the query.

Another thing to keep in mind is that the queries and data types used to return data are by design different from those used to request data. Mutation queries need to validate incoming data, so the types used for those queries are meant to serve that function. Likewise, the fields used in returned query objects are for display, not validation. If you take in a GraphQL object as returned from a query, it might have fields with circular references or other issues that make it unusable as a query parameter.

Why use GraphQL

A key reason to choose GraphQL over REST is the explicit, declarative nature of GraphQL queries. Having a formal definition for how queries and returned data should look has advantages aside from being consistent across APIs and implementations.

As Phil Sturgeon noted in his examination of GraphQL vs. REST, the field structure of GraphQL makes it easier to apply more granular versioning to queries, since specific fields can be deprecated or rolled in over time as opposed to versioning the entire API. It’s still possible to take the wholesale versioning approach with GraphQL; the point is that you aren’t forced to do so when rolling out changes.

Sashko Stubailo, engineering manager at Apollo GraphQL, maker of open source tools for GraphQL APIs, notes another advantage of the GraphQL approach: It’s self-documenting. “Every possible query, object, and field comes with a name, description, and type information that can be queried from the server in a standard way,” Stubailo writes.

The self-documenting nature of GraphQL provides a sort of “introspection,” meaning you can use queries to return information about themselves. This way, software that works with GraphQL queries doesn’t have to be hard-wired to work with any particular field set; it can infer the fields automatically.

That said, the fact that GraphQL is newer and REST/Swagger are older should not be by itself a reason to favor the former. As Arnaud Lauret, author of The Design of Everyday APIs put it in a discussion of the two standards, “A GraphQL API, just like a REST one, must be created with a purpose and designed from an outside-in perpective and not an inside-out one.”

Copyright © 2018 IDG Communications, Inc.