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:
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.
I have organized some of the reasons why you should use GraphQL for this project. Theses include:
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.
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.
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.
Before we proceed, it’s better to familiarize ourselves with GraphQL terms that will be mentioned often:
A prebuilt type for querying our data, more like a GET request in a REST API.
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.
A function that connects schema fields and types to various backends.
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.
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.
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.
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:
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.
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:
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.
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:
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)
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:
We have reached the end of our article; I hope this article was useful to you!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
4 Replies to "Build a GraphQL API with Python, Flask, and Ariadne"
super super helpful. Just one minor note, re “uvicorn hello_word:app”, i think you meant “uvicorn hello_world:app”
Hi there,
Thanks for reading the LogRocket blog! We’ve corrected the typo.
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?
The code is slightly out of date, the current ariadne version (0.21) doesn’t have the PLAYGROUND_HTML constant anymore which causes this code to fail. https://ariadnegraphql.org/docs/flask-integration shows how to use the explorer instead.