Coding a GraphQL API With Python. Using Flask, Ariadne, and… | by Eric Chi | Dec, 2022

Using Flask, Ariadne, and Flask-SQLAlchemy

Image made by the author.

Complete source code can be found here: https://github.com/ericjaychi/sample-graphql

Before we code anything, we should probably quickly go over what GraphQL is. GraphQL is a query language for APIs. What does that mean? It means that a developer interacting with your API can request as much or as little data as they want when it comes to the request. Think of how you can query a relational database for as many or as few columns as you like given a table.

Now the big question is, how does this differ from a REST API and why should you use it? If you don’t know what a REST API is, that’s fine, I have an article on how to create one! I would recommend looking at this tutorial as the complexity is a little lower than a GraphQL API before beginning this tutorial.

Now, let me try to paint a picture. Imagine going into a grocery store with prepackaged sandwiches at the deli. These sandwiches have bread, lettuce, tomatoes, cheese, meat, and mayonnaise. You can’t customize them; it’s “grab and go.” Now, let’s say now you have become vegetarian and are also on a low-carb diet but still want a sandwich from the deli. These pre-packaged sandwiches still suffice, but when you open the sandwich packaging, you have to remove some items that you don’t want like the meat and bread.

Now, imagine this same deli, but instead of prepackaged sandwiches, there is a build-your-own sandwich bar that contains all the same ingredients. You are now at liberty to make any sandwich you want based on your dietary restrictions.

REST APIs are the prepackaged sandwiches, while GraphQL APIs are the build-your-own sandwich bar in this analogy. There are pros and cons to each kind of deli just like how there are pros and cons from a software and architecture perspective. Now that the stage is set, let’s go ahead and get into the implementation!

Table of Contents

Step 1: Installing Dependencies

Step 2: Setting Up the Flask Environment

Step 3: Defining the Database and the Models

Step 4: Populating the Database

Step 5: Defining GraphQL Schemas

Step 6: Creating and Binding Resolvers

Step 7: Executing Our API

Step 8: Mutating Data

For an API like this, we will need three components: Flask, Ariadne, and Flask-SQLAlchemy.

  • Flask is a framework to allow us to build web servers in Python.
  • Ariadne is a library for us to leverage GraphQL.
  • Flask-SQLAlchemy is a library that lets us spin up a quick database. Any other database solution could be used, but for this tutorial, we will be using something simple like Flask-SQLAlchemy.

All three of these packages are accessible on pip which means it’s easy to install onto our machine. This assumes you have pip installed on your machine or are using some form of virtual environment.

pip install flask ariadne flask-sqlalchemy
Installing dependencies through pip. Ignore the warnings, Homebrew is deprecating pip and I’ve been lazy to move over.

I won’t be going into detail on what these libraries do, if you are curious feel free to search them up online!

Now that we have the tools that we will be using installed, let’s go ahead and get our application up and running first before we add any sort of complexity from the GraphQL code.

To do this, we will need to create a small file structure within our directory. First, create a new file called main.py inside of your project directory. Second, you want to create an api/directory and then a file named __init__.pyinside of that directory. In the end, you’ll have this:

sample-graphql-api
├── api
│ └── __init__.py
├── book.db
└── main.py

Inside the contents of the main.pyfile:

main.py

Then inside of the api/__init__.py file:

api/__init__.py

This is pretty much just boilerplate code that will allow us to run the application on our local machine. We will soon be adding some more functionality to this.

Let’s go ahead and run the application by typing in this command inside the project directory:

python main.py
The output of starting the Flask application.

Running this command will get a local server running. You can now go to 127.0.0.1:5000 inside your browser, to see our lovely webpage! localhost:5000 also works.

http://127.0.0.1:5000/

Nothing fancy, but this is required for us to start iterating on our next steps.

The next step will be for us to write some code to define the database models for this tutorial. Since we are interacting with Flask-SQLAlchemy, there isn’t any work we will need to do to spin a local database up, however, we still do need to add a few lines of code to configure the database into a file and we still need to define our database models on the Python side.

Let’s start by first defining the database and attaching it to the Flask application within the __init__.py file:

api/__init__.py

Again, since we are using Flask-SQLAlchemy, it only takes a few lines of code to do this as opposed to if we were using a database solution.

  • Line 10 is telling Flask-SQLAlchemy exactly where the database is located.
  • Line 11 is a configuration that tracks modifications of objects that sends a signal to the application every time there is a database change. This isn’t necessary for our tutorial so we have set it to False.

Now that we have the database file in place, we can go ahead and define the data model that we will be using for this example in a new file called models.py. This file will be created inside of the api directory. Here’s the code:

api/models.py

This is relatively straightforward code as most applications will need to represent the data model within the backend language that is being used. This allows us to represent our data model on the Python side that will communicate with the database.

Now that our database code is all implemented, we can now add some dummy data into the database via the Python command line. Traditionally you would have data come from the user, but since this is a tutorial on using GraphQL, we simply need dummy data to work with.

Before we move to the next step, we also want to finish configuring our package, so add this new import statement below the existing import statement inside of main.py. Your import statements should look like this in main.py:

from api import app, db
from api import models

In this step, we will be focusing on getting some data into our local database. Normally, we would have data be populated from a different source like end users, but since this is a tutorial, we will need to add data ourselves.

To do this, we will need to invoke the Python interpreter inside our terminal while inside the project directory. Simply type the following line into your terminal:

python

This will open up your Python interpreter. From here, we will manually create a record inside of our database as shown:

Type each command (lines with >>>) into the interpreter. We could in theory make a file to do this, but this is faster.

Now that we ran this bit of code, we should have a new file called book.db inside of our directory. This contains the data we just put in along with some other data needed to have the database run properly.

Now that we have our data properly created, it’s time to finally get into the GraphQL part of this tutorial. To quickly summarize, GraphQL has its own language, SDL, Schema Definition Language. This defines what our API has to offer. Both from a data model perspective as well as what is offered from an endpoint perspective.

We are going to need to create a new file inside the root directory of this project called schema.graphql that will offer CRUD (Create, Read, Update, Delete) functionality. We will only be covering Create and Read in this tutorial.

schema.graphql

Keep in mind that this step can be done at the beginning of defining the API. It doesn’t really matter as long as both your server-side code is respecting the definitions inside of schema.graphql and vice versa.

In this step, we will be creating resolvers and then binding them so that we can use them to invoke our API. Resolvers are functions or methods that resolve a value for a type within a schema we have created. It might be better if I just show you what I mean here.

Within the api/directory, please create a new file called queries.py that look like this:

api/queries.py

This is a pretty simple function if you look at it. Inside the get_books_resolver() function, we query theBook table for all the items and serialize it into a dictionary such that we can pass it as the response. There is a snippet that allows us to catch an error if one is presented, and the response will be set according to the error that surfaced.

obj acts as a value returned by a parent resolver, which for this specific case is the root resolver. This is not leveraged in the tutorial.

info contains any context that the GraphQL server provides during the resolver execution. This can carry a wide range of data like authentication details. This is not leveraged in the tutorial.

Now that we have a basic resolver written, we need to let our framework, Ariadne, know how to execute this function. Inside of our main.py we are going to want to add the following code snippet:

main.py

We have quite a bit of code that was added here, so let’s go through some of the important pieces.

ObjectType is imported to help us define what type of execution this resolver is within our schema. There are two types that we defined, Queryand Mutation. In this case, we are defining ObjectTypeto be the Querytype that is synonymous to a GET in the world of REST APIs.

set_field binds the books to our resolver. What does that mean? When talking to our API, the client or user will be using what we define in the first parameter of this set_field function. So that means our user must use the books keyword to invoke whatever function we define, in this case, get_books_resolver().

load_schema_from_path reads whichever schema we decide to input. In this case, the schema.graphql file we made in the previous step.

make_executable_schema takes the schema and query we defined and allows them to be executed.

snake_case_fallback_resolvers allows conversions of field names to snake case before looking it up on the returned object. This is more just to keep things consistent since Python uses snake case while most other languages like Javascript use camel case.

We are nearing the finish line in terms of actually having something to show for all of our hard work. We are going to leverage Ariadne’s GraphQL Playground. This a UI that allows us to play around with our API. We need to add some more code in order to enable this though.

In your main.py file, add the following snippet after the resolver logic:

Bottom of main.py

This piece of code allows us to use the /graphql destination within our browser. This allows us to use the Playground UI that Ariadne provides. To understand what I’m saying, go to inside your browser. Be sure that you are running your Flask server by executing python main.py inside of your terminal!

The Ariadne Playground UI

Now on the left side of the editor, insert this following snippet:

query getAllBooks 
getBooks
success
errors
books
id
title
author
isbn


When you execute this by pressing the giant play button, you will see that the output of all the different Books inside of of our database on the right side:


"data":
"getBooks":
"books": [

"author": "James Clear",
"id": "1",
"isbn": "0735211299",
"title": "Atomic Habits"

],
"errors": null,
"success": true


After executing our query.

As you can see the model is structured exactly how we defined it inside of the schema.graphql as well as our models.py file.

The interesting part of GraphQL as mentioned in the beginning of this tutorial is that you can request as much or little data as you’d like, unlike a traditional REST API. To do this, simply alter the query that we are submitting to remove whichever fields that you don’t need!

query getAllBooks 
getBooks
success
books
id
title
author


Here in this query, we removed the isbn field as well as the error field and when we execute this query, we won’t see those data points inside of the response. So this really gives a ton of power to the client to request exactly what data they need instead of getting every piece of data based on the API contract. This is one of the points that make GraphQL extremely powerful.

The final step in this very long tutorial, altering the data! As altering data is just as important as retrieving the data. The good thing is that it repeats the same steps as Step 6, except we are writing logic to alter data instead of retrieving data. To shorten this tutorial just a bit, we will only be adding the creation logic of a Book. Any other operations follow the exact same steps, with the difference being the core logic to populate the data in the response.

To make a Mutation, which is the GraphQL vocabulary for altering data, we are going to create a new file called mutations.py inside of our api directory. This will be the home where our mutations live similar to how we have a home for our queries. Inside of this newly created file, add the following lines of code:

api/mutations.py

I feel like the code is self explanatory here since we did this back in Step 6.

Now that we have injected our resolver into our mutations.py file we will need to configure it in our main.py file so that our application understands how to invoke this piece of code.

Inside of our main.py file we are going to want to update our code to look like this:

main.py

We added a few lines of code.

  • Line 8 we are importing our new resolver that we defined earlier in this step.
  • Line 15 we are creating a new ObjectType with the value of the “Mutation” type we have defined inside of our schema.
  • Line 20 acts very similar to our query counterpart, except we are calling the the create_book_resolver function.
  • Line 27 we need to update our parameter list to take in our new mutation variable we defined.

Let’s restart our Flask server real quick after these changes have been made. Now, we can go ahead and execute our new mutation inside of the UI with the following statement:

mutation createBook 
createBook(title:"Eat That Frog!", author:"Brian Tracy", isbn:"152309513X")
success
errors
book
id
title
author
isbn


This invokes our resolver that we defined with the matching function we defined inside of schema.graphql under the type Mutation section. The parameter list is exactly as we defined in both the schema.graphql file and our resolver. Then the resolver handles the data as we coded it, then passes the response back to the client.

Once you execute this within the UI, you should get this response back:


"data":
"createBook":
"book":
"author": "Brian Tracy",
"id": "2",
"isbn": "152309513X",
"title": "Eat That Frog!"
,
"errors": null,
"success": true


Remember, GraphQL allows us to ask for as much or little data as possible. Here in our response we are receiving all the data since that was what was defined in the request, but if we wanted to just limit it to specific data points within our model, we can remove fields within our query, similar how we did that in the previous step via getAllBooks.

Now if you execute getAllBooks, you will see the newly added book!


"data":
"getBooks":
"books": [

"author": "James Clear",
"id": "1",
"title": "Atomic Habits"
,

"author": "Brian Tracy",
"id": "2",
"title": "Eat That Frog!"

],
"success": true


We did it! Congrats on making it to the end. This was an extremely long tutorial, and we even skipped some operations like updating, deleting, and getting a book by an ID. The process will all be the same, except the fact that you are changing the core logic within your resolver. As long as you understand how to bind a resolver, then you can create any endpoint in theory.

I encourage you to try to implement the rest of the operations on your own.

As always, all of the code can be found here: https://github.com/ericjaychi/sample-graphql-api

I’ll see you all in the next one!

Leave a Reply