Comparative notes on GraphQL and REST API

 

REST has been the de-facto API standard for quite sometime since Roy Fielding introduced it in his thesis in 2000. GraphQL is the new kid on the block who wants to address some of the issues with REST, but in no way a replacement or improved version of REST. 

REST is a standard/philosophy, GraphQL is a query language and has GraphQL engine implementations in different programming languages to be able to interpret and respond to those GraphQL queries.

Can we compare them, REST and GraphQL?

As is wont to happen, human minds do tend to compare things and do tend to learn and register and reinforce better by comparing things ( or else why am I taking these comparative notes? 😉). Straight answer to the above question is 'No'. A more sensible thing would be to compare a REST API unsupported by GraphQL vs one supported by GraphQL.

What is GraphQL?

Lets see what the official site graphql.org has to say:

"GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more..."

The below are noteworthy:

  • It has a specific query language.
  • It has a runtime (read GraphQL engine).
  • It has schema (for understandable description of the data).
  • It provides only the bare minimum i.e. what the client has exactly queried and nothing more.

Specific query language

The query is somewhat JSON-ish (at least that is how I tend to relate to it - a JSON blob minus the values.)

A simple example:

{
  book {
    isbn
    title
    author
    publish_year
    publish_version
    kindle_version
    category 
    price
  }
}

A somewhat simple example: (with argument passing and data transformation)

{
  book(isbn: '978-3-16-148410-0') {
    title
    author
    publish_year
    publish_version
    kindle_version
    category 
    price(unit: POUND)
  }
}

A complex example:

// Example of alias
{
  first_book: book(isbn: '548-3-66-198410-0') {
    title
    author
    referenced_books {
        name
    }
  }
  second_book: book(isbn: '978-3-16-148410-0') {
    title
    author
    referenced_books {
        name
    }
  }
}
// Abbreviating the above query with "fragments"
{
  leftComparison: book(isbn: '548-3-66-198410-0') {
    ...comparisonFields
  }
  rightComparison: book(isbn: '978-3-16-148410-0') {
    ...comparisonFields
  }
}

fragment comparisonFields on ISBNObject {
  title
  author
  referenced_books {
    name
  }
}
// If you are wondering about ISBNObject, it is the Type name of the query 
// field 'isbn' - this'll be later shown in the schema section.

[ NSSC (Not-so-sensible-comparison) : Do REST APIs have a query language? No, they don't. HTTP has request parameters and values which get a little close to it in the sense that they will be used by the API endpoints but can't really launder them has query language, can we?]

GraphQL engine or library or query reference

In one sentence, a GraphQL library/engine is the interface provider to GraphQL clients to the actual resource which can be REST APIs or any Microservices or any Database. Apollo server, Ariadne and Graphene are prominent examples.

A few key concepts of GraphQL engines are:

Resolver: 

Resolvers define how a query field or attribute ends up pulling corresponding data. Example below in the form of resolve_<field> functions of Graphene Types

from graphene import ObjectType, String, Schema
from pprint import pprint
import random
import json

books = {
            '548-3-66-198410-0': {
                'isbn': '548-3-66-198410-0',
                'title': 'Dummy_book_1',
                'author' : 'Debashish',
                'publish_year' : '2002',
                'publish_version' : '15',
                'kindle_version' : 'none',
                'category' : 'Programming',
                'price' : 'INR 150'},
            '978-3-16-148410-0': {
                'isbn': '978-3-16-148410-0',
                'title': 'Dummy_book_2',
                'author' : 'Debashish',
                'publish_year' : '2002',
                'publish_version' : '2',
                'kindle_version' : 'none',
                'category' : 'Design',
                'price' : 'INR 200'}
        }

class Query(ObjectType):
    # field  in our Schema with a single Argument `name`
    book = String(isbn=String(default_value="978-3-16-148410-0"))
    title = String(isbn=String(default_value='548-3-66-198410-0'))
    author = String()
    publish_year = String()
    publish_version = String()
    kindle_version = String()
    category = String()
    price = String()

    # Resolver method takes the GraphQL context (root, info) as well as isbn argument
    # This is just a simple example, without any backend
    def resolve_book(root, info, isbn):
        _book_obj = books.get(isbn)
        return(json.dumps(_book_obj))

    def resolve_title(root, info, isbn):
        _book_obj = books.get(isbn)
        return(json.dumps(_book_obj.get('title')))

schema = Schema(query=Query)

if __name__ == '__main__':
    # we can query for our field (with the default argument)
    query_string = '{ book(isbn: "548-3-66-198410-0") title }'
    result = schema.execute(query_string)
    pprint(result.data, indent=4)

Schema-first:

GraphQL schema needs to be explicitly define. Consider the following Apollo Server example:

const { ApolloServer, gql } = require('apollo-server');
// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
const typeDefs = gql`
  # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.
  # This "book" type defines the queryable fields for every book in our data source.
  type book {
    isbn: String
    title: String
    author: String
    publish_year: String
    publish_version: String
    kindle_version: String
    category: String
    price: String
  }
  # The "Query" type is special: it lists all of the available queries that
  # clients can execute, along with the return type for each. In this
  # case, the "books" query returns an array of zero or more Books (defined above).
  type Query {
    books: [book]
  }
`;

Code-first:

GraphQL SDL(Schema Definiton Language) is wrapped around with native language objects and hence schema need not be defined separately, rather it is part of the code. The Graphene resolver example given earlier has shown a code-first approach.

[ NSSC: REST APIs do not need a runtime as all languages inherently understand HTTP and any REST endpoints can do sort of resolving requests to different resources based on URL paths and parameters] 

Schema

GraphQL has a Schema Definition Language(SDL) with interesting Object Types like Query, Mutation, Interface, Union in addition to basic types like ID, String, Float etc. This is a good resource from the official site.

[ NSSC: REST APIs may use JSON schema optionally but it is nowhere near the Type system seen in GraphQL] 


Response

The responses are usually very simple 😌. They are nothing but JSON docs. The important thing to remember here is though - the client can control what fields would be returned and in what format in the response. We can draw a db SQL analogy - users get to see only the queried data fields in the query result.

Successful response format:

{
  "data": { ... },
  "extensions": { ... }
}

Error response format:
{
  "errors": [ ... ],
}

[ NSSC: REST API responses are JSON only if the endpoint chooses to return it as so. ] 

Comments

Popular Posts