GraphQL with Python: Tutorial with server and API examples
In recent years, GraphQL has gained significant traction among Python developers seeking more efficient ways to build and consume APIs. This query language for APIs offers a fresh approach to data fetching and manipulation, particularly appealing in Python's versatile ecosystem.
New to GraphQL? Check out the Introduction to GraphQL tutorial to learn the core concepts quickly.
Understanding GraphQL in the Python Ecosystem
Python's ecosystem provides an interesting playground for GraphQL implementation:
- Extensive Library Support: Libraries like Graphene and Ariadne facilitate smooth GraphQL integration.
- Scalability: Python's scalability complements GraphQL's efficient data loading.
Ready to explore GraphQL with Python? The next section will guide you through setting up your first Python GraphQL server, demonstrating how they can work together effectively.
Setting Up a Python GraphQL Server
Implementing a GraphQL server in Python is a fairly standard process, thanks to the ecosystem of libraries and tools available. This section will guide you through the essentials of setting up a Python GraphQL server, highlighting various approaches to get your API up and running quickly.
Choosing Your GraphQL Framework in Python
Fundamentally, you can build a GraphQL server with schema first, code first or a domain driven approach. Several Python frameworks support GraphQL implementation. Here are some popular options:
- Graphene: A Python library for building GraphQL schemas/types.
- Strawberry: A new library for creating GraphQL APIs using Python type hints.
- Ariadne: A schema-first GraphQL library for Python.
- graphql-core: Python reference implementation of the GraphQL spec.
Graphene is one of the most widely used schema first GraphQL libraries for Python.
- Object-oriented schema definition
- Integration with popular web frameworks (Django, Flask)
- Support for relay specification
Strawberry is a library that leverages Python's type hints for GraphQL schema definition.
- Type-first approach using Python 3.7+ type hints
- Code-first schema definition
- Built-in support for dataclasses
There are some parameters to look at while choosing the right library/framework for building a GraphQL server in Python.
Choosing the right Python GraphQL library/framework:
- For Django/Flask Integration: Consider Graphene
- For Schema-First Approach: Look at Ariadne
- For Type Hints and Modern Python: Try Strawberry
- For Low-Level Control: Use graphql-core
- For domain-driven: Use Hasura for bootstrapping the API and integrate Python for business logic
Each has its pros and cons, but for this guide, we'll focus on Graphene and Strawberry due to its widespread adoption and extensive documentation.
Creating a Python GraphQL Server with Graphene
- First, set up a virtual environment and install the necessary packages:
python -m venv graphql_envsource graphql_env/bin/activate # On Windows, use `graphql_env\Scripts\activate`pip install graphene Flask graphene-flask
- Define your GraphQL schema using Graphene:
import grapheneclass Query(graphene.ObjectType):hello = graphene.String(name=graphene.String(default_value="World"))def resolve_hello(self, info, name):return f'Hello {name}'schema = graphene.Schema(query=Query)
- Integrate your GraphQL schema with Flask:
from flask import Flaskfrom flask_graphql import GraphQLViewapp = Flask(__name__)app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True))if __name__ == '__main__':app.run(debug=True)
- Run your GraphQL server:
python app.py
Visit http://localhost:5000/graphql to access the GraphiQL interface.
Create a Python GraphQL Server with Strawberry
We can make a custom GraphQL server in Python using Strawberry.
Run the Strawberry FastAPI quickstart
In
remoteSchema.py
add the Strawberry codeimport strawberryfrom strawberry.fastapi import GraphQLRouter@strawberry.typeclass Query:@strawberry.fielddef hello(self) -> str:return "Hello World"schema = strawberry.Schema(Query)graphql_app = GraphQLRouter(schema)Add the generated GraphQL handler to
main.py
from remoteSchema import graphql_appapp.include_router(graphql_app, prefix="/graphql")
While the above setup works for a simple Hello World API, it's worth considering more efficient approaches:
Schema Generation: Instead of manually defining resolvers, consider tools that can generate schemas from your database models. Batched Resolvers: Implement batching to reduce the number of database queries. Caching: Implement a caching layer to improve performance for frequently accessed data.
How do I handle authentication in my GraphQL server? Implement authentication middleware in your Flask app and access it within your resolvers.
Can I use async/await with Python GraphQL servers? Yes, libraries like Ariadne support asynchronous resolvers for improved performance.
How do I connect my GraphQL server to a database? You can use ORMs like SQLAlchemy or integrate directly using database-specific Python libraries.
The key to a successful GraphQL implementation lies in understanding your data relationships and designing your API thoughtfully.
Implementing mutations with Python
In this section, we will explore how to implement and use mutations in a Python GraphQL environment, emphasizing best practices for data modification.
A typical GraphQL mutation will look like this:
mutation createUser($name: String!, $email: String!) {createUser(name: $name, email: $email) {idname}}
The simplest prototype implementation of this mutation in Graphene will look like this:
import grapheneclass CreateUser(graphene.Mutation):class Arguments:name = graphene.String(required=True)email = graphene.String(required=True)user = graphene.Field(lambda: User)def mutate(self, info, name, email):user = User(name=name, email=email)# Add logic to save user to databasereturn CreateUser(user=user)class Mutation(graphene.ObjectType):create_user = CreateUser.Field()schema = graphene.Schema(mutation=Mutation)
The logic to save user to the database will include ORM library usage and client SDKs to various databases.
Some of the Best Practices for Mutations:
- Input Validation: Always validate input data before processing.
- Error Handling: Provide clear error messages for failed mutations.
- Atomicity: Ensure that mutations are atomic - they should either complete fully or not at all.
- Return Updated Data: Return the modified object in the mutation response.
One of the way to optimize for performance is to batch the mutations: Group related mutations to reduce network requests.
What are the different ways to optimize mutation execution?
While manually implementing mutations works well for many cases, consider leveraging tools that can:
- Automatically generate CRUD mutations based on your data model
- Handle authentication and authorization checks
- Provide real-time updates to subscribed clients
For instance, some advanced GraphQL engines can automatically create efficient mutations from your database schema, reducing boilerplate code and ensuring consistency between your data layer and API. Hasura does this across any data source, for example.
Building a Python GraphQL Application: A Tutorial
In this section, we'll walk through creating a simple yet functional GraphQL application using Python. This tutorial will demonstrate how to set up a GraphQL server, define a schema, and implement queries and mutations.
Let's setup the project environment:
mkdir python-graphql-tutorialcd python-graphql-tutorialpython -m venv venvsource venv/bin/activatepip install flask graphene flask-graphql
We'll create a simple book library application. Here's the structure:
python-graphql-tutorial/├── app.py└── models.py
We will start by defining data models. In models.py, we'll define our data models:
class Author:def __init__(self, id, name):self.id = idself.name = nameclass Book:def __init__(self, id, title, author_id):self.id = idself.title = titleself.author_id = author_id# Sample dataauthors = [Author(1, "J.K. Rowling"),Author(2, "J.R.R. Tolkien")]books = [Book(1, "Harry Potter and the Philosopher's Stone", 1),Book(2, "The Hobbit", 2)]
In app.py, let's create the GraphQL schema:
from flask import Flaskfrom flask_graphql import GraphQLViewimport graphenefrom models import Author, Book, authors, booksclass AuthorType(graphene.ObjectType):id = graphene.ID()name = graphene.String()class BookType(graphene.ObjectType):id = graphene.ID()title = graphene.String()author = graphene.Field(AuthorType)def resolve_author(self, info):return next(author for author in authors if author.id == self.author_id)class Query(graphene.ObjectType):books = graphene.List(BookType)authors = graphene.List(AuthorType)def resolve_books(self, info):return booksdef resolve_authors(self, info):return authorsschema = graphene.Schema(query=Query)app = Flask(__name__)app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True))if __name__ == '__main__':app.run(debug=True)
Now, we can run the app python app.py
. Visit http://localhost:5000/graphiql
to start exploring the queries. You can try out the following query:
query {books {idtitleauthor {name}}}
Let's add the ability to create new books. Update app.py
:
class CreateBook(graphene.Mutation):class Arguments:title = graphene.String(required=True)author_id = graphene.Int(required=True)book = graphene.Field(lambda: BookType)def mutate(self, info, title, author_id):book = Book(id=len(books) + 1, title=title, author_id=author_id)books.append(book)return CreateBook(book=book)class Mutation(graphene.ObjectType):create_book = CreateBook.Field()schema = graphene.Schema(query=Query, mutation=Mutation)
You can try out the following mutation to create a new book:
mutation {createBook(title: "The Hitchhikers Guide", authorId: 2) {book {idtitleauthor {name}}}}
For the above queries to be performant, you might have to implement DataLoader to batch and cache database queries for better performance.
How can I connect this to a real database? Replace the in-memory lists with database queries using an ORM like SQLAlchemy.
Can I add authentication to this API? Yes, you can implement authentication middleware in Flask and access it in your resolvers.
How do I handle file uploads? Use libraries like graphene-file-upload to handle file uploads in mutations.
GraphQL Queries with Python
A GraphQL query in Python (or any language/framework) typically consists of three main components:
- The query string
- Variables (optional)
- The execution method
Let's break these down:
Here's how a query string would look like:
query getUser($id: String!) {user(id: $id) {nameposts {title}}}
The variable for the above query can be defined in Python as:
variables = {"id": "123"}
Once we have these defined, the execution depends on how the GraphQL server is exposing the API. Typically the API is exposed as a HTTP endpoint or Websocket (in case of a realtime app). In this example, let's look at HTTP request execution with Python.
Executing GraphQL queries using Python requests library
The execution in Python for the GraphQL query will be as follows:
from gql import gql, Clientfrom gql.transport.requests import RequestsHTTPTransporttransport = RequestsHTTPTransport(url='http://your-graphql-endpoint')client = Client(transport=transport, fetch_schema_from_transport=True)query = gql('''query getUser($id: String!) {user(id: $id) {nameposts {title}}}''')result = client.execute(query, variable_values=variables)print(result)
How do I handle errors in GraphQL queries? GraphQL returns errors in a standardized format. Check the
errors
key in the response.Can I use GraphQL queries with asynchronous Python code? Yes, libraries like
gql
support async operations with asyncio.
FAQs
Is GraphQL suitable for all Python projects? While not universal, GraphQL can benefit projects of various sizes, especially those requiring flexible data fetching.
How does GraphQL handle database operations in Python? GraphQL itself is database-agnostic. Tools like Hasura can auto-generate GraphQL APIs from your database schema, simplifying the process.
Can GraphQL improve API performance in Python applications? Yes, by reducing over-fetching and allowing batched queries, GraphQL can significantly enhance API efficiency.
Summary
As we've explored throughout this post, GraphQL offers significant advantages for building flexible and efficient APIs in Python ecosystems. However, the traditional resolver-based approach, while intuitive, often leads to performance challenges and increased complexity as applications scale. This is where domain-driven compiler-style GraphQL APIs shine, providing a superior solution for Python developers looking to harness the full power of GraphQL.
New to Hasura? The Hasura GraphQL Engine makes your data instantly accessible over a real-time GraphQL API so that you can build and ship modern, performant apps and APIs 10x faster. Hasura connects to your databases, REST and GraphQL endpoints, and third-party APIs to provide a unified, connected, real-time, secured GraphQL API for all your data. Check out the Hasura documentation.
See the server source code on Github.
If you use Hasura and are ready to go to production, check out Hasura Cloud for a fully managed Hasura deployment.
- Build apps and APIs 10x faster
- Built-in authorization and caching
- 8x more performant than hand-rolled APIs