GraphQL Server with Ariadne and Flask

Creating a quick, schema-first GraphQL API with Python.

Birk Astrup
Netcompany
6 min readNov 4, 2019

--

I work at Netcompany which delivers business critical solutions to clients all over northern Europe. Recently, I got the opportunity to put together an API and choose which technologies to use. I decided to go for a Python Flask API that serves data with GraphQL.

Netcompany provides IT solutions for both large enterprises and governments

In this article I’ll show you an example of how easy it is to set up a simple GraphQL server with Flask, using Ariadne.

Getting Things Ready

Before we begin you need to have Python and pip installed on your system. For this project I’ll be using:

  • Python 3.7.4
  • Pipenv 2018.11.26
  • Flask 1.1.1
  • Ariadne 0.7.0

Pipenv is a nifty tool that brings together virtualenv with pip so you don’t have to use them separately. This way you can install dependencies with pipenv, and they’ll stay in the scope of the project without having to use virtualenv to create a project environment. You can read more about pipenv here.

As the title suggest we’ll be using the Flask microframework to make a super basic API. With flask we can keep the implementation really bare-bones or add to it should the need arise.

Ariadne is a simple, pythonic Schema-first GraphQL library that makes creating GraphQL APIs a breeze. Just define a schema of what kind of data you can retrieve with the API, make some resolvers which handles the different fields in the schema and bind the resolvers to the fields.

I will not go into the details of what Schema-first GraphQL is in this article, but Jakub Draganek has written an excellent article on the subject which I highly recommend.

Creating the API

Let’s give you what you came for. The API we’ll be making is nothing fancy, it retrieves dummy data on buildings and their residents from these json files. Just make a new folder in the project directory called data and put the files there if you want to use them.

First thing first, we need to get things ready. Install pipenv if you haven’t already. In your terminal execute:

pip install –user pipenv

Then install the required dependencies in the root folder of the project.This will install the dependencies, create a Pipfile and a Pipfile.lock file along with a virtual environment for the project. Now let’s create a file called app.py where our routing and schema binding will take place. First, we need to import the necessary dependencies.

Flask doesn’t handle async out-of-the-box so we need to use the graphql_sync package from Ariadne. For asynchronous apps you would switch out graphql_sync for GraphQL and define async functions, the rest is the same.

With the proper dependencies in place, let’s configure our Flask app.

This is really all we need for a functional Flask app. To run the application, navigate to the project root folder where the Pipfile is located and execute:

pipenv run flask run

If you navigate to http://127.0.0.1:5000/graphql you should now see this:

GraphQL Playground

The route that handles GET requests takes you to the GraphQL playground where you can test out your queries and mutations. The route that handles POST requests processes your queries. This route won’t do anything right now as we have no queries or mutations. To remedy this we first need to create a schema.

Defining the Schema

We now have our Flask app up and running. Now to fetch something with GraphQL. As stated previously, Ariadne which we use as our GraphQL implementation is schema-first, this means we must define a schema of what kind of data we can expect from the API. In this example, we’ll create separate file to hold our schema, and since I’m bursting with creative energy we’ll name it schema.graphql.

In the schema we define a Building type, a Resident type and a Query type. Within these types we define scalars which can be called “leaf-values”. They are comparable to primitives and unlike types they don’t return additional fields, i.e. the field buildYear vs residents on the Building type. Buildyear returns a String and therefore is a scalar. While residents return a list of type resident.

The query type defines the queries that can be sent to the API. Right now, we have two queries:

  • BuildingById which returns a building.
  • ResidentById which returns a resident.

A schema is the first step to a functioning GraphQL API, but let’s bring it in to our Flask app so we can run some queries.

Earlier we imported the load_from_path package from Ariadne. Let’s use it to define our schema types from schema.graphql in our Flask app. Then we need to create an instance of QueryType for the query defined the schema.

The last line is added because to use Python logic with our schema, we need to bind our types and resolvers to our schema. Ariadne provides a function for this, make_executable_schema, which we imported earlier.

make_executable_schema takes in the type definitions we loaded from the schema and a list of bindables. You can read more about bindables in the docs.

If we were to execute one of the queries defined in the query type now, we would get an error. This is because we have no resolvers for the fields in the queries yet.

Adding Resolvers

Resolvers are functions that “resolves” values for the different fields in the defined schema. If the resolved value is of another type defined in the schema, we would need to implement a resolver for that field as well. In our case, we need to resolve the fields by reading data from the json files. For this example, we’ll use synchronous resolvers, but they can also be asynchronous.

There are several ways to implement resolvers with Ariadne. In this example we’ll define our resolvers in a separate module and import them to app.py. For other ways to create resolvers check the Ariadne docs.

Create a new file and call it resolvers.py. As mentioned earlier, we’re going to retrieve data from json files and to do that, we need to import the json package from the standard python library.

Let’s begin by defining a resolver for the Building_with_id query.

The resolver takes in three parameters: obj, info and _id. Long story short, obj is a value from the parent resolver. Our resolver is the root resolver, as it resolves a filed defined in the Query type. This means that obj will be None for this resolver. Info contains application specific data such as an HTTP request. Again, more on the subject can be read here. _id is set by us when we query the API.

Our resolver returns a building from the buildings.json file with an id matching the _id parameter.

Now to implement the resolver in the flask app we just import it in app.py and map it to appropriate field.

Things have been pretty straight forward so far. query which is an instance of QueryType() has a method set_field() which sets r.building_with_id as a resolver for the field building_with_id on the Query type in the schema. With this in place, we have a functional GraphQL API.

Well, almost…

At the moment, we can only return one building with our API. If we were to do something like this:

Query a building and it’s residents.

Then residents would output as null. Remember that if a resolver returns a value that is of another type defined in the schema, we need to implement a resolver for that field as well.

In the resolvers.py module, we define the following function:

Let’s implement it as a resolver like we did with building_with­_id() in app.py:

Now the residents field will be resolved and will output to the result.

We haven’t implemented a resolver for the family field or the resident_with_id field. This, I leave up to you to implement. The process is the same as with the previous resolvers.

To Sum It All Up

My experience with Flask and Ariadne has been very positive. Even though this article barely scratches the surface of what you can do with Ariadne and Python, I hope I managed to show you how easy it is to get started with schema-first GraphQL and Python. Moving forward, I would like to try my hand at making the API asynchronous and implement subscriptions, schema directives and custom scalars.

Thank you!

--

--