GraphQL, the new Contender to REST

by
Tags: , ,
Category:

The Representative State Transfer (REST) protocol has been the king of remote access protocols for web applications for well over a decade. The general pattern: expose “nouns” (Customers, Activities, Employees, Tasks, Sasquatches) as URLs (/api/sasquatch/32) and access them via HTTP “verbs” such as “GET”, “POST” (create), “PUT” (update), or “DELETE” (umm, well…). The content type is specified via HTTP headers such as Content-Type (for data being received by the client) and Accepts (for a data request).

The reason this works so well is that the HTTP protocol, devised by Roy Fielding in a Doctoral Thesis all those years ago, can describe just about anything. It usually devolves to exceptions to the rule (how do you handle commands? POST em?), but you can secure the traffic with SSL/TLS, you can represent the data in any format (XML, JSON, BSON, Protocol Buffers), and you can understand the type of request by reading it like a sentence:

GET /api/customers

Accepts: application/json,text/plain

(I want to get all of my customers. When you send them to me, first try to retrieve them as JSON data, but if you can’t do that, I’ll take plain text. Just tell me with your response).

The Challenge: Death by a thousand endpoints…

As with all thrones, there are always contenders (and pretenders). The old SOAP protocol was ditched in favor of REST because of its simplicity and universality.

For a while in the late ’aughts, Flex (a Flash-based front-end that died because Steve Jobs decided Flash would never run on his phones) had an RPC-based remoting technology, BlazeDS, that was blisteringly fast. Spring Remoting has existed for a long time, but having a Java-based Applet or Java Web Start front-end is not what people want.

As long as we front our applications with web servers and HTTP, a challenger must live in that world.

Enter GraphQL. It exposes a single HTTP endpoint (/graphql for example) with a POST request, therefore living in the HTTP and SSL/TLS world, but sends queries to that endpoint using a query language. It provides (and exposes) a schema and metadata querying tools to define queries, modifications and data types. The data in the response is JSON-compatible, and the calls can be as coarse-grained or fine-grained as you need them to be.

REST -vs- RPC

So often, developers run into situations with REST where they have to learn within the noun-verb contraption, or just exit out with special POST APIs that don’t look super RESTful. For example:

POST /api/jobs/execute?job=tps-reports
POST /api/customers?nameLike=Fred&city=Catasaqua&numRows=300

It might be better to provide an endpoint that accepts RPC-style requests, like:

runTpsReports()
findCustomersByNameAndCity('Fred', 'Catasaqua', 300)

If the data returned is the acknowledgement of the job, it could either be a promise that resolves successfully with no payload, or possibly with a boolean or number. So you could run it via a promise:

try {
  await runTpsReports();
} catch (e) {
    console.log('it failed.', e);
    // handle error
}
// it worked

Or even a simple finder function, like this.

let customers;
try {
    customers = await findCustomersByNameAndCity(name, city, 300);
} catch (e) {
    console.log('it failed', e);
    // handle error
}

Before, you’d have to translate this into a particular REST call, and each resource would need its own REST endpoint. With GraphQL, you expose a schema, bound to resources on the backend, that you query.

Let’s look at a fictional GraphQL query for finding customers:

query findCustomers {
  Customers {
    id
    name
    city
  }
}

No matter how big the “customers” type is, we can grab only the fields we want. If the “customers” type has an “orders” type embedded as a collection, we can just ask for those as well, even drilling down into the product attached to the order, if so defined:

query findCustomers {
  Customers {
    id
    name
    city
    orderSummary {
      orderId
      orderDate
      quantityOrdered
      totalAmount
      product {
        name 
      }
    }
  }
}

Queries can be pre-defined, especially if they limit the number of rows or take parameters in code. For example, a query can be issued that is given a customer search pattern for name, a city, and the number of rows to return:

query findCustomersByNameAndCity($name, $city, $numRows) {
   findCustomersByNameAndCity(
     name: $name!, city: $city, numRows: $numRows) {
     id
     name
     city
     orderSummary {
       orderId
       orderDate
       totalAmount
     }
   }
}

Right away we see that we have some degrees of freedom as a client. First, we can see that our parameters are naturally written via RPC-style calling semantics. The $name (required), $city (optional) and $numRows (optional, an int that is 300 if not passed) parameters come in via our call. The fields we want sent across the network are decided at the query, not by the server. And we can even ask for a graph of structures (if so provided by the API).

Here is a GraphQL Schema definition to handle the queries above:

type Customer {
    id: ID!
    name: String!
    city: String!
    orderSummary: [OrderSummary!]
}

type OrderSummary {
    orderId: ID!
    orderDate: String!   (hold that thought)
    totalAmount: Float
    product: Product!
    quantityOrdered: Float!
}

type Product {
    productID:  ID!
    name: String!
    description: String
    quantityOnHand: Float!
}

query {
    findCustomersByNameAndCity(
       name: String!, city: String, numRows: Int = 300): [Customer!]!
}

Most of this schema can be easily read once we discuss some rules:

  • type keywords define types that can be passed or returned by queries.
  • query defines queries that can be made from an external system, and are bound to methods or functions, or even data mapping facilities by the serving GraphQL engine.
  • GraphQL comes with several built-in types, like ID, String, Float, and Boolean… Hence the comment about strings. Usually an ISO-8601-formatted Date is consumable by most applications.
  • Parameters can have default values, such as the numRows query parameter in our method above.
  • Schemas just define what, not how – that’s something you have to bring to GraphQL. You still have to write the actual function to run your query. But you had to do that anyway with REST.

Major GraphQL Features

GraphQL isn’t just a query tool. It’s a fully-fledged remote application protocol that includes:

  • Mutations (queries that change your data and optionally return data like queries)
  • Subscriptions (queries that push updates to the caller, usually via WebSockets)
  • Metadata queries
  • Built-in monitoring capabilities

All of this and it’s JSON up and down, so you can even query GraphQL with the curl command line.

GraphQL Implementations

There are a number of GraphQL implementations (servers and clients) available for a wide variety of languages. Here is a small list:

  • JavaScript has a number of implementations, including the Reference Implementation GraphQL.js, the popular Apollo GraphQL suite of APIs, and the Facebook Relay client, which includes React components.
  • Java has the GraphQL for Java API as well as the Spring Boot GraphQL API which is based on it.
  • Python’s Graphene
  • Scala’s Sangria
  • Groovy’s Gorm GraphQL and Grails plugin
  • Amazon’s AppSync for cloud-based GraphQL servers (see below)

Each GraphQL implementation implements the query language, type system, and protocol, but may vary in how API calls are wired, exposed, and managed.

Give it a try

GraphQL has a lot going for it over using plain-old REST. Since the type system and schemas are exposed on the endpoint, lots of third-party query tools exist (GraphQL even has its own GraphQL Playground) that can test queries, provide code completion, and allow passing of parameters. Check out Altair for an alternative desktop query tool.

Amazon AppSync

An exciting implementation of GraphSQL is Amazon’s AppSync, which serves database resources, Lambdas and other AWS infrastructure as GraphQL. It supports subscriptions, can be developed as the backend of an AWS Amplify application, which adds some code generation annotations for things like CRUD operations (the @model) annotation for access to DynamoDB tables, a @connection annotation for linking fields to relationships of other tables (think of it as a foreign key), and @auth for connecting to security in Amazon’s IAM authorization management system. See more about that in the Amplify SDK documentation.

You can see more details on the GraphQL query language in my Philly Emerging Tech talk PDF, Video, or just by following the tutorials on GraphQL.org. I’ll have more posts on GraphQL in the future that go into specifics of various implementations or techniques.