Kalebu Gwalugano Kalebu is a mechatronics engineer and professional Python developer passionate about innovation, open source, and technical writing. He writes mostly on Python, data science, and machine learning (ML/AI).

Build a GraphQL API with Python, Flask, and Ariadne

7 min read 2098

Build a GraphQL API with Python, Flask, and Ariadne

When it comes to API development, there are several architectural styles you can choose from based on your API requirements, each one having its own strength and weakness. These approaches include:

  • Simple Object Access Protocol (SOAP)
  • Representational State Transfer (REST)
  • gRPC Remote Procedure Call (gRPC)
  • GraphQL

Here is what I found on Google Trends. By comparing how frequently people search for the API architectural styles, we can clearly see that REST is way ahead of others and also the most familiar among developers. But don’t overlook this insight: GraphQL is only here about six years since its public release by Facebook on 2015, while REST has been here since early 2000.

Google Trends for API Architecture

Why GraphQL?

I have organized some of the reasons why you should use GraphQL for this project. Theses include:

Only fetch what you need

GraphQL allows you to avoid over fetching and under fetching by giving you the capability to query data from multiple resources, thus eliminating the need to have multiple endpoints and having to make multiple API calls to get the data you want.

No more versioning

Since GraphQL allows you to make changes to the API internals (fields, resolvers, etc.) without having to change the resource URL, this saves you from the headaches of having to manage versions of your API manually together with updating the client codebase.

Strong type schema (less error-prone)

GraphQL uses strongly typed schema, which means when you specify the types in your schema, it’s going to handle all the type validations for you. This saves you from debugging errors caused by invalid types.

GraphQL terms

Before we proceed, it’s better to familiarize ourselves with GraphQL terms that will be mentioned often:

Query

A prebuilt type for querying our data, more like a GET request in a REST API.

Mutation

A prebuilt type for manipulating our data. Every field in the Mutation type can be thought of as a POST/PUT/DELETE/PATCH request in a REST API.

Resolver

A function that connects schema fields and types to various backends.

Field

A unit of data that belongs to a type in your schema.

You can learn more about all the terms from the official GraphQL documentation.

Getting started with Ariadne

Ariadne uses a schema-first approach while other libraries used to implement GraphQL in Python, like Graphene and Strawberry, use a code-first approach. You might be wondering what separates these two.

The main difference is that schema-first indicates that we first define the schema for the GraphQL service and then we implement the code by matching the definitions in the schema. In the code-first approach, we start by coding the resolvers, and then, from code as a single source of truth, we have the schema generated as an artifact.

You can learn more about the difference between the schema-first approach and the code-first approach here.

Installation requirements

Now that we have a good idea of what GraphQL and Ariadne are, let’s install all the required libraries and see how it’s all implemented codewise.



pip install ariadne, uvicorn, flask, flask-sqlalchemy, flask-migrate

uvicorn is an ASGI server that we will be using to run our GraphQL API before integrating with Flask.

Flask is a micro web framework written in Python. It’s one of the top preferred frameworks in backend development.

Flask-SQLAlchemy and Flask-Migrate are extensions for handling interaction with the database. Flask-SQLAlchemy provides ORM abstraction while Flask-Migrate provides a way to handle database migrations.

“Hello, world!”: Build a simple GraphQL API

Let’s create a simple GraphQL API with Ariadne that returns a list of destinations to visit. Our code is going to look like this:

from ariadne.asgi import GraphQL
from ariadne import gql, QueryType, make_executable_schema

# Define type definitions (schema) using SDL
type_defs = gql(
   """
   type Query {
       places: [Place]
   }


   type Place {
       name: String!
       description: String!
       country: String!
       }  
   """
)

# Initialize query

query = QueryType()

# Define resolvers
@query.field("places")
def places(*_):
   return [
       {"name": "Paris", "description": "The city of lights", "country": "France"},
       {"name": "Rome", "description": "The city of pizza", "country": "Italy"},
       {
           "name": "London",
           "description": "The city of big buildings",
           "country": "United Kingdom",
       },
   ]

# Create executable schema
schema = make_executable_schema(type_defs, query)

# Create ASGI application
app = GraphQL(schema)

Now that our code for our simple API is ready, we can then run it with uvicorn as shown below, assuming the script is titled hello_world.py:

uvicorn  hello_world:app

You can visit the GraphQL Playground on your browser at http://127.0.0.1:8000/ to interact with your GraphQL API and dynamically query the places based on fields you need as shown in the GIF below:

Visit GraphQL Playground

Integrating Ariadne with Flask

Now that we know how Ariadne works, it’s time to see how we can integrate it with Flask. No new libraries are required for this; we are only going to change a few things.

You might be wondering why dare to integrate with Flask if it’s possible to run Ariadne independently using the uvicorn ASGI server. Well, this integration helps utilize the existing Flask ecosystem (flask extensions and features) without reinventing the wheel.

For instance, handling database integration using extensions such as Flask-SQLAlchemy, Flask-MongoDB, Flask-Migrate, and so on.

“Hello, world!”: Flask + Ariadne

In order to transform the world the “Hello, world!” we made in the previous example, we need to add two routes that will be handling two functionalities, which were previously being handled by the inbuilt ASGI web server. This includes:

  • a route to get the client request to pass it to executable schema and then return back a response to the client
  • a route to serve as a Playground client to easily interact with a client (you might not need this one in production)

Here’s how the final code is going to look like after integrating it with Flask:

from ariadne.constants import PLAYGROUND_HTML
from flask import Flask, request, jsonify
from ariadne import gql, QueryType, make_executable_schema, graphql_sync

# Define type definitions (schema) using SDL
type_defs = gql(
   """
   type Query {
       places: [Place]
   }


   type Place {
       name: String!
       description: String!
       country: String!
       }  
   """
)

# Initialize query

query = QueryType()

# Define resolvers
@query.field("places")
def places(*_):
   return [
       {"name": "Paris", "description": "The city of lights", "country": "France"},
       {"name": "Rome", "description": "The city of pizza", "country": "Italy"},
       {
           "name": "London",
           "description": "The city of big buildings",
           "country": "United Kingdom",
       },
   ]

# Create executable schema
schema = make_executable_schema(type_defs, query)

# initialize flask app
app = Flask(__name__)

# Create a GraphQL Playground UI for the GraphQL schema
@app.route("/graphql", methods=["GET"])
def graphql_playground():
   # Playground accepts GET requests only.
   # If you wanted to support POST you'd have to
   # change the method to POST and set the content
   # type header to application/graphql
   return PLAYGROUND_HTML

# Create a GraphQL endpoint for executing GraphQL queries
@app.route("/graphql", methods=["POST"])
def graphql_server():
   data = request.get_json()
   success, result = graphql_sync(schema, data, context_value={"request": request})
   status_code = 200 if success else 400
   return jsonify(result), status_code

# Run the app
if __name__ == "__main__":
   app.run(debug=True)

When you run the app, it will automatically launch on http://localhost:5000/ and you can view the playground to interact with the GraphQL server by visiting http://localhost:5000/graphql. The results for this app are going to behave exactly like our first example without Flask.

Adding mutations

Let’s add some mutations to our app to allow us to add new places. We’re going to need to update two main parts, the query and the resolver, for this. We are going to add a new mutation add_place (name, description, country) that takes the name, description, and country as parameters and then creates a resolver that will append new places to the list.

Our final code is going to look like this:

from ariadne.constants import PLAYGROUND_HTML
from flask import Flask, request, jsonify
from ariadne import gql, QueryType, MutationType, make_executable_schema, graphql_sync

# Define type definitions (schema) using SDL
type_defs = gql(
   """
   type Query {
       places: [Place]
   }


   type Place {
       name: String!
       description: String!
       country: String!
       }  

   type Mutation{add_place(name: String!, description: String!, country: String!): Place}
   """
)

# Initialize query

query = QueryType()

# Initialize mutation

mutation = MutationType()

# Define resolvers

# places resolver (return places )
@query.field("places")
def places(*_):
   return places

# place resolver (add new  place)
@mutation.field("add_place")
def add_place(_, info, name, description, country):
   places.append({"name": name, "description": description, "country": country})
   return {"name": name, "description": description, "country": country}

# Create executable schema
schema = make_executable_schema(type_defs, [query, mutation])

# initialize flask app
app = Flask(__name__)

# Create a GraphQL Playground UI for the GraphQL schema
@app.route("/graphql", methods=["GET"])
def graphql_playground():
   # Playground accepts GET requests only.
   # If you wanted to support POST you'd have to
   # change the method to POST and set the content
   # type header to application/graphql
   return PLAYGROUND_HTML

# Create a GraphQL endpoint for executing GraphQL queries
@app.route("/graphql", methods=["POST"])
def graphql_server():
   data = request.get_json()
   success, result = graphql_sync(schema, data, context_value={"request": request})
   status_code = 200 if success else 400
   return jsonify(result), status_code

# Run the app
if __name__ == "__main__":
   places = [
       {"name": "Paris", "description": "The city of lights", "country": "France"},
       {"name": "Rome", "description": "The city of pizza", "country": "Italy"},
       {
           "name": "London",
           "description": "The city of big buildings",
           "country": "United Kingdom",
       },
   ]
   app.run(debug=True)

Here is an example of how to make mutation requests to our GraphQL Flask server:

GraphQL Mutation Request

Adding the database (Flask-SQLAlchemy + Flask-Migrate)

Now that you’re now comfortable working with Flask and Ariadne, you can then begin integrating other components to the application, including the database. Instead of storing the data to the list of dictionaries, we can store data to the database using Flask-SQLAlchemy. We can also integrate with Flask-Migrate to manage migrations to our database.

Here is an example of the GraphQL API with similar functionalities to the previous code with mutations. The only difference is that this code is using the real database instead of storing the data on a list of dictionaries:

from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from ariadne.constants import PLAYGROUND_HTML
from flask import Flask, request, jsonify
from ariadne import gql, QueryType, MutationType, make_executable_schema, graphql_sync

# Define type definitions (schema) using SDL
type_defs = gql(
   """
   type Query {
       places: [Place]
   }


   type Place {
       name: String!
       description: String!
       country: String!
       }  

   type Mutation{add_place(name: String!, description: String!, country: String!): Place}
   """
)

# Initialize query

query = QueryType()

# Initialize mutation

mutation = MutationType()

# Define resolvers

# places resolver (return places )
@query.field("places")
def places(*_):
   return [place.to_json() for place in Places.query.all()]

# place resolver (add new  place)
@mutation.field("add_place")
def add_place(_, info, name, description, country):
   place = Places(name=name, description=description, country=country)
   place.save()
   return place.to_json()

# Create executable schema
schema = make_executable_schema(type_defs, [query, mutation])

# initialize flask app
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite3"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)

class Places(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   name = db.Column(db.String(80), nullable=False)
   description = db.Column(db.String(255), nullable=False)
   country = db.Column(db.String(80), nullable=False)

   def to_json(self):
       return {
           "name": self.name,
           "description": self.description,
           "country": self.country,
       }

   def save(self):
       db.session.add(self)
       db.session.commit()

# Create a GraphQL Playground UI for the GraphQL schema
@app.route("/graphql", methods=["GET"])
def graphql_playground():
   # Playground accepts GET requests only.
   # If you wanted to support POST you'd have to
   # change the method to POST and set the content
   # type header to application/graphql
   return PLAYGROUND_HTML

# Create a GraphQL endpoint for executing GraphQL queries
@app.route("/graphql", methods=["POST"])
def graphql_server():
   data = request.get_json()
   success, result = graphql_sync(schema, data, context_value={"request": request})
   status_code = 200 if success else 400
   return jsonify(result), status_code

# Run the app
if __name__ == "__main__":
   app.run(debug=True)

Migrating to the database

Since we have added a new database model for Places, we need to do migrations. First, to initialize the database and then migrate our newly created model to our local database, you can do this by using the below command:

export FLASK_APP=hello_world.py # assuming you named your script hello_world.py
flask db init 
flask db migrate
flask db upgrade 

After doing migrations, we are ready to run our application as shown below:

python  hello_world.py

The interaction on the GraphQL Playground will look quite similar to the previous one:

GraphQL Playground Final Demo

We have reached the end of our article; I hope this article was useful to you!


More great articles from LogRocket:


References

  1. https://daily.dev/blog/graphql-terminology-cheatsheet
  2. https://www.apollographql.com/docs/resources/graphql-glossary/
  3. https://ariadnegraphql.org/
  4. https://graphql.org/learn/

 

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Kalebu Gwalugano Kalebu is a mechatronics engineer and professional Python developer passionate about innovation, open source, and technical writing. He writes mostly on Python, data science, and machine learning (ML/AI).

3 Replies to “Build a GraphQL API with Python, Flask, and Ariadne”

  1. super super helpful. Just one minor note, re “uvicorn hello_word:app”, i think you meant “uvicorn hello_world:app”

    1. Hi there,

      Thanks for reading the LogRocket blog! We’ve corrected the typo.

  2. Great tutorial. I used to create a graphql endopoint in my app. Now I need to create some filters to use in the query, do you know where I can find it?

Leave a Reply