How to build a GraphQL wrapper for a RESTful API in Python

Published Nov 26, 2017Last updated Jan 18, 2018

I wanted to give GraphQL a shot for a while now. After all, the cool kids at Github are doing it, so I wanted to learn what all the hype is all about. From what I’ve seen so far it looks like GraphQL would likely supersede REST the same way SOAP got obsoleted by more lightweight stateless JSON-based APIs. From what I’ve seen so far, one of the biggest advantages in for my work would be the following:

Rapid iteration on the frontend by decoupling client from server

You don’t need to make any modifications on the API if you decided that you want to access different data on the frontend, as you can specify declaratively what data you need on, rather than how to get it.

If you want to learn more about GraphQL and key differences with REST, I highly recommend this article.

Getting started

For the purpose of this tutorial, I’m using the following components:

In this tutorial, I’ll show you how to define schema on your backend and transform JSON into GraphQL models.

Concretely, we would query reviews for a given Airbnb listing as an example.

Below is a sample of a response we get from our API:

{  
   'reviews':[  
      {  
         'author':{  
            'first_name':'Maryann',
            'has_profile_pic':True,
            'id':40613795,
            'picture_url':'https://a0.muscache.com/im/pictures/c77f54a1-c3d0-4742-93af-f2e53b8f5145.jpg?aki_policy=profile_x_medium',
            'smart_name':'Maryann',
            'thumbnail_url':'https://a0.muscache.com/im/pictures/c77f54a1-c3d0-4742-93af-f2e53b8f5145.jpg?aki_policy=profile_small'
         },
         'author_id':40613795,
         'can_be_edited':False,
         'comments':"We had a wonderful time staying at the silo cottage! Even though the cottage is located off the beaten path, the directions that were provided were very clear and helpful. Denise and her husband were very kind and accommodating. When we arrived Denise's husband was there to guide us to the cottage and later was even so kind to give us a tour of the larger silo. The first night my boyfriend and I stayed at the cottage we unfortunately lost the keys and had to call her husband late at night. But Denise's husband was very understanding and gave us a spare key with no extra charge. Additionally he checked in to make sure we were comfortable and had enough wood pellets for the stove. The inside of the cottage is clean and cozy, it was very enjoyable to sit inside reading a book by the warm pellet stove. Overall we had an amazing time staying at the silo cottage and would definitely stay there again!",
         'created_at':'2017-11-22T21:13:28Z',
         'id':213704794,
         'language':'en',
         'listing':{  
            'id':1238125,
            'name':"'Silo Studio' Cottage"
         },
         'listing_id':1238125,
         'recipient':{  
            'first_name':'Denise',
            'has_profile_pic':True,
            'id':6636440,
            'picture_url':'https://a0.muscache.com/im/pictures/b96e3e9b-cc2b-4d29-aa47-b0be63c74432.jpg?aki_policy=profile_x_medium',
            'smart_name':'Denise',
            'thumbnail_url':'https://a0.muscache.com/im/pictures/b96e3e9b-cc2b-4d29-aa47-b0be63c74432.jpg?aki_policy=profile_small'
         },
         'recipient_id':6636440,
         'response':'',
         'role':'guest',
         'user_flag':None
      },
  ...
  ]
}

As you can see we get an array of reviews inreviews key in our JSON dictionary.

Now what I would love to do is just to be able to automatically map all the keys of a given review from the JSON payload to respective keys in our internal data model. Unfortunately, there is no such way as far as I know of, so we’d have to define our data models manually.

Defining schema

As you can see in the previous JSON response, we have a few nested objects inside our review: author, recipientand listing. To transform those inner structures, we’d want to create additional classes. From the look of it author and recipient have the same format, so we can combine them in User class, and we would create another class forListing as well.

Now we can refer those internal structures from Review simply as Field(User) and Field(Listing).

The only thing left to do is to define respective data types for the rest of the fields. GraphQL and Graphene provide the following base scalar types: String, Int, Float, Boolean, ID. For more information about defining custom data types, read here.

That said, I’ve ended up with the following schema defined with Graphene:

from graphene import ObjectType, String, Boolean, ID, Field, Int

class User(ObjectType):
    first_name = String()
    has_profile_pic = Boolean()
    id = ID()
    picture_url = String()
    smart_name = String()
    thumbnail_url = String()

class Listing(ObjectType):
    id = ID()
    name = String()

class Review(ObjectType):
    author = Field(User)
    author_id = ID()
    can_be_edited = Boolean()
    comments = String()
    created_at = String()
    id = Int()
    language = String()
    listing = Field(Listing)
    listing_id = ID()
    recipient = Field(User)
    recipient_id = ID()
    response = String()
    role = String()
    user_flag = Boolean()

Defining Query

When it comes to interfacing our data model to the JSON and doing the transformation of the payload to python objects, here is where the things get sort of confusing.

Our goal is to define the following GraphQL query, which fetches reviews for a given listing, for example, to fetch all comments for the listing with id 1238125, we’d use the following query:

{
  reviews(id:1238125) {
    	comments
    }
}

To represent the query above with a Graphene, we’d need to define Query class as follows:

from graphene import ObjectType, List

class Query(ObjectType):
    reviews = List(Review, id=Int(required=True))

Here we map the field reviews from our JSON response to a List of Review objects defined earlier. We also specify that our GraphQL query would have a single non-optional parameter: id.

Now we need to map this field to an API call. To do that we need to define a resolver which would actually do the network call. Resolvers are static methods defined on Graphene object type classes which have the following format by default: resolve_{field_name}.

Converting JSON to Python objects

The only thing left to do is to convert our JSON dictionary to objects, so that instead of calling reviews[0]["role"] we would be able to call reviews[0].role. According to Graphene design, objects are expected to be returned by a resolver. So here is what we are going to do: serialize our JSON, then deserialize it into objects using object_hook which would convert everything into named tuples:

def _json_object_hook(d):
    return namedtuple('X', d.keys())(*d.values())

def json2obj(data):
    return json.loads(data, object_hook=_json_object_hook)

Finally, here’s how our final resolver would look like:

class Query(ObjectType):
    reviews = List(Review, id=Int(required=True))

    def resolve_reviews(self, args, context, info):
        reviews = api_call(args.get("id"))["reviews"]
        return json2obj(json.dumps(reviews))

Here, the api_call actually calls the RESTful API using id parameter from our GraphQL query.

Putting it all together

Great! We finally figured out how to map our RESTful API to GraphQL. Now I’ll show you how to build a simple Flask-based backend serving that GraphQL goodness.

We can put all of the scheme-related code in schema.py and create a simple Flask app only with a few lines of code:

from flask import Flask
from schema import Query
from flask_graphql import GraphQLView
from graphene import Schema
import os


view_func = GraphQLView.as_view(
    'graphql', schema=Schema(query=Query), graphiql=True)

app = Flask(__name__)
app.add_url_rule('/graphql', view_func=view_func)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=os.environ.get('PORT', 5000))

Here I’m referencing Query from schema.py and enabling GraphiQL which works as an interactive browser for your GraphQL API.

Now, you can start the app, go to http://localhost:5000/graphiql and try your brand new GraphQL API!

I’ve deployed the app to Heroku, so that you can try it yourself!

Finally, all of the code used in this tutorial is available for you on Github for further tinkering. Have fun!

Discover and read more posts from Nikolay Derkach
get started