top of page

Getting started with GraphQL in Python with FastAPI and Graphene



What is GraphQL

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, makes it easier to evolve APIs over time, and enables powerful developer tools

GraphQL was internally developed by Lee Byron and his team in 2012 and used in many internal projects including the Facebook mobile app and later it was released publicly in 2015.


The official website provides an excellent resource for learning different parts of GraphQL which you can refer to for details. Here, I will be more discussing the implementation in Python. Don’t worry, I will be sharing a few bits of the basics anyway.


Before I move forward, let’s set up our local dev environment. Like previous FastAPI posts, I will be using Pipenv here as well. I am not going to repeat all steps but the installation of Graphene.


What is Graphene

Graphene-Python is a library for building GraphQL APIs in Python easily, its main goal is to provide a simple but extendable API for making developers’ lives easier.

In nutshell, it helps you to set up GraphQL features easily.


GraphQL provides a playground for testing your GraphQL queries. It inputs the URL of the GraphQL server and lets you test queries. I will be setting up the playground first and then we will start coding our application. The application is the same: the contact management system.

import graphene
from fastapi import FastAPI
from starlette.graphql import GraphQLApp

class Query(graphene.ObjectType):
    hello = graphene.String(name=graphene.String(default_value="stranger"))

def resolve_hello(self,info,name):
        return "Hello " + name

app = FastAPI(title='ContactQL', description='GraphQL Contact APIs', version='0.1')
@app.get("/")
async def root():
    return {"message": "Contact Applications!"}

app.add_route("/graphql", GraphQLApp(schema=graphene.Schema(query=Query)))

After importing graphene I am import GraphQLApp. GraphQL facility was actually provided by Starlette. Since FastAPI is using Starlette hence it is available for us automatically.


When I access http://localhost:8080/grapgql it shows the following interface:



Don’t panic about what is written here. All I want to reveal that the fancy interface you are seeing above is available only because of the line app.add_route("/graphql", GraphQLApp(schema=graphene.Schema(query=Query)))


OK so now we have our playground ready for running different queries. It’s time to learn the basics of GraphQL queries.


Queries

The very first thing you need to know about GraphQL is querying a graphql server. You follow a certain syntax:

{
  hello
}

The thing you are seeing above is actually calling an object, in this case, hello is an object. You pass a JSONish like query to graphql server and it returns:

{
  "data": {
    "hello": "Hello stranger"
  }
}

As you can see the graphql query is pretty much like the returned JSON.

query {
 hello 
}

You can explicitly pass the query keyword with the query for better understanding.


Alright, the above query was executed in a fancy GraphQL interactive interface but life is not so fancy in real. In order for frontend apps to interact with your graphql server, you have to access it like you’d do with a typical REST API. Below is the cURL version of the above:

curl 'http://127.0.0.1:8000/graphql' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"{hello}"}' \
  --compressed

And in Postman it looks like:



Sounds cool, No? The GET version opens the interactive client while POST call actually is used for executing GraphQL queries. In production code, you might not like to provide the graphiql interface to your users. You can simply disable it by passing graphiql=False in GraphQLApp constructor. But I am keeping it as it for testing purposes.


Let’s start the real application. In the previous post, I made the necessary DB and model-related files so I am just using them here. I have copied the models folder and database.py file here. The current folder structure looks like the below:



OK, so now I am going to make a few changes in main.py file.

import graphene
from fastapi import FastAPI
from starlette.graphql import GraphQLApp

from models.contact import list_contacts

class Contact(graphene.ObjectType):
    first_name = graphene.String()
    last_name = graphene.String()
    email = graphene.String()class QueryContact(graphene.ObjectType):
    contacts = graphene.List(Contact)

@staticmethod
    def resolve_contacts(self, info):
        records = []
        contacts = list_contacts()
        for c in contacts:
            records.append({'first_name': c.first_name, 'last_name': c.last_name, 'email': c.email})

return records


After importing all necessary stuff I am creating a class Contact of type graphene.ObjectType. The reason for doing this is that we have to expose certain fields of contacts. Since the purpose is to show the list of contacts which we then pass to graphene.List that accepts a class type. You can learn more about it here.

After creating the class I am creating a field contacts in QueryContact class which is of type List. You can pass any type in it. In our case, it is of Contact type.


The code in the resolver method is self-explanatory. It is fetching records from contacts table and appending in records list table



here, the root object is contacts. In case you wonder why is it showing contacts. It is because we defined a field with the name in QueryContact class of type List. All the sub-fields are being fetched from the class of type Contact which itself is of ObjectType.


So whatever the field(make sure they are part of the system) you pass they’d be visible here. Below are the cURL and Python requests version of executing the above, just in case one needs to integrate graphql in a system.





The above one was returning all items but what if I want to return a single item or items that match certain criteria? Graphql gives you an option to pass arguments in your queries. This is what you do:

{
  contacts(id:1) { 
     firstName
     email
  }
}

As you see I am passing a parameter, id here to filter out results. The code will now look like this:

class QueryContact(graphene.ObjectType):
    contacts = graphene.List(Contact, id=graphene.Int())

@staticmethod
    def resolve_contacts(self, info, id=0):
        records = []
        result = None

if id == 0:
            contacts = list_contacts()
            for c in contacts:
                records.append({'first_name': c.first_name, 'last_name': c.last_name, 'email': c.email})
            result = records
        elif id > 0:
            contact = get_contact(id)
            if contact is not None:
                result = [{'first_name': contact.first_name, 'last_name': contact.last_name, 'email': contact.email}]
            else:
                result = []
        return result


the contacts field has passed an argument id of type graphene,Int(). Once done, we'd be passing a default id parameter in resolve_contacts method. The rest of the code should be clear. I am checking if an id is being passed then call get_contact otherwise list_contacts(). Since we are already using contacts of type list so even a single record returns it'd be wrapped in a list.


Cool, isn’t it? If you want to map these queries with RDBMS’ SQL queries so it’d be like: SELECT first_name,email from contacts and for a single query, it'd be SELECT first_name,email from contacts where id = 2.


Mutation

So far we are pulling the data, what if we want to change it, mutation allows you to manipulate data. The mutation query syntax looks like the below:

mutation {
  createContact(firstName:"Musab",lastName:"Siddiqi",email:"musab@gmail.com",phone:"123-0393") {
    id
    firstName
  }    
}

Let’s write the code!


from models.contact import list_contacts, get_contact, create_contact

class CreateContact(graphene.Mutation):
    # These fields will be displayed after successful insert
    id = graphene.Int()
    first_name = graphene.String()

class Arguments:
        first_name = graphene.String()
        last_name = graphene.String()
        email = graphene.String()
        phone = graphene.String()

def mutate(self, info, first_name, last_name, email, phone):
        new_contact = create_contact(first_name=first_name, last_name=last_name, email=email, phone=phone, status=1)

if new_contact is None:
            raise Exception('Contact with the given email already exists')

return CreateContact(id=new_contact.id, first_name=new_contact.first_name)

class Mutation(graphene.ObjectType):
    create_contact = CreateContact.Field()

app.add_route("/graphql", GraphQLApp(graphiql=True, schema=graphene.Schema(query=QueryContact, mutation=Mutation)))


So I created a CreateContact class that inherited a class Mutation.


First I defined a couple of fields. These fields actually show the data after the data insertion or deletion/updation( Yes, the mutation is available for any kind of data manipulation). Then, Arguments class is used to pass arguments, these arguments contain the pieces of info which will be inserted. The mutate method actually is similar to the resolve_ methods. The return of this method is the object of the class CreateContact. We are doing this because we have to show those fields in return. Then, I am creating a class inherited ObjectType. The class contains the field create_contact which is of type CreateContact.


Finally, graphene.Schema will be passed another parameter that sets the Mutation class. The resultant of the code will be the below:



First, it creates a new record. When you try to insert the same record w.r.t email it raises an exception, cool, No?


Conclusion

In this post, you learned how you can create graphql based queries for your application. I have only discussed the basics of it. In case you are interested to learn in-depth, you should explore the documentation of GraphQL and graphene. Like always, the code is available on Github.



Source: medium-Adnan Siddiqi


The Tech Platform


bottom of page