Are you looking to supercharge your FastAPI applications with the flexibility of GraphQL? In this step‑by‑step tutorial we’ll walk you through everything you need to know to integrate GraphQL into a Python FastAPI project, from setting up the environment to defining schemas, writing resolvers, and testing your new API. Whether you’re a seasoned Python developer or just getting started with FastAPI, this guide will give you a solid foundation for building modern, data‑driven services that scale.
Why Combine FastAPI and GraphQL?
FastAPI is already famous for its speed, automatic OpenAPI documentation, and type‑hint‑driven development. Adding GraphQL brings several complementary benefits:
- Client‑driven queries: Clients request exactly the data they need, reducing over‑fetching and under‑fetching.
- Single endpoint: One flexible endpoint replaces dozens of REST routes.
- Strong typing: GraphQL schemas map naturally to Python type hints, keeping your codebase consistent.
- Real‑time capabilities: Subscriptions enable live updates without extra infrastructure.
Choosing a GraphQL Library for FastAPI
There are three popular GraphQL integrations that work seamlessly with FastAPI:
- Ariadne – Schema‑first approach, excellent for developers who prefer SDL (Schema Definition Language).
- Strawberry – Code‑first, leverages Python type hints to generate the schema automatically.
- Graphene‑FastAPI – A wrapper around Graphene, the original Python GraphQL library.
In this tutorial we’ll use Ariadne because its clear separation of schema and resolvers makes debugging easier, and it integrates effortlessly with FastAPI’s dependency injection system.
Project Setup
1. Create a virtual environment
python -m venv venv
source venv/bin/activate # On Windows use `venv\Scripts\activate`
2. Install required packages
pip install fastapi uvicorn[standard] ariadne
3. Project structure
Organize your files as follows:
my_fastapi_graphql/
│
├─ app/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ schema.graphql
│ └─ resolvers.py
└─ requirements.txt
Defining the GraphQL Schema
Save the following SDL in app/schema.graphql. This simple schema models a Book type and provides queries for fetching books and a mutation for adding a new one.
type Book {
id: ID!
title: String!
author: String!
publishedYear: Int
}
type Query {
books: [Book!]!
bookById(id: ID!): Book
}
type Mutation {
addBook(title: String!, author: String!, publishedYear: Int): Book!
}
Implementing Resolvers
Resolvers contain the business logic that executes when a client runs a query or mutation. Create app/resolvers.py and add the following code:
from ariadne import QueryType, MutationType
from typing import List, Dict
# In‑memory data store (replace with a real DB in production)
BOOKS: List[Dict] = [
{"id": "1", "title": "Clean Code", "author": "Robert C. Martin", "publishedYear": 2008},
{"id": "2", "title": "Effective Python", "author": "Brett Slatkin", "publishedYear": 2015},
]
query = QueryType()
mutation = MutationType()
@query.field("books")
def resolve_books(*_):
"""Return the full list of books."""
return BOOKS
@query.field("bookById")
def resolve_book_by_id(*_, id):
"""Find a single book by its ID."""
for book in BOOKS:
if book["id"] == id:
return book
return None
@mutation.field("addBook")
def resolve_add_book(*_, title, author, publishedYear=None):
"""Create a new book and return it."""
new_id = str(len(BOOKS) + 1)
new_book = {
"id": new_id,
"title": title,
"author": author,
"publishedYear": publishedYear,
}
BOOKS.append(new_book)
return new_book
Wiring Ariadne with FastAPI
Now we’ll expose the GraphQL endpoint using FastAPI’s routing system. Edit app/main.py as follows:
from fastapi import FastAPI
from ariadne import load_schema_from_path, make_executable_schema, graphql
from ariadne.asgi import GraphQL
from .resolvers import query, mutation
app = FastAPI(title="FastAPI + GraphQL Demo")
# Load the SDL schema
type_defs = load_schema_from_path("app/schema.graphql")
# Create executable schema
schema = make_executable_schema(type_defs, [query, mutation])
# Mount GraphQL endpoint
graphql_app = GraphQL(schema, debug=True)
app.add_route("/graphql", graphql_app)
app.add_websocket_route("/graphql", graphql_app)
# Optional: Add a simple health check
@app.get("/health")
async def health_check():
return {"status": "ok"}
Running the Server
Start the application with Uvicorn:
uvicorn app.main:app --reload
Navigate to http://localhost:8000/graphql in your browser. Ariadne provides an interactive GraphQL Playground where you can test queries and mutations instantly.
Testing Your GraphQL API
Query Example: Get All Books
query {
books {
id
title
author
publishedYear
}
}
The response should list the two sample books defined in the in‑memory store.
Mutation Example: Add a New Book
mutation {
addBook(title: "Python Tricks", author: "Dan Bader", publishedYear: 2020) {
id
title
author
publishedYear
}
}
After running the mutation, repeat the books query to see the newly added entry.
Adding Authentication with FastAPI Dependencies
One of FastAPI’s strengths is its dependency injection system. You can protect GraphQL resolvers by injecting a user object or token validation function. Below is a minimal example using a fake token header.
from fastapi import Header, HTTPException
async def get_current_user(authorization: str = Header(...)):
if authorization != "Bearer secret-token":
raise HTTPException(status_code=401, detail="Invalid token")
return {"username": "demo_user"}
# Apply the dependency to resolvers
@query.field("books")
def resolve_books(_, info):
user = info.context["request"].state.user # injected later
# You can now use `user` for fine‑grained access control
return BOOKS
To make the user object available in info.context, modify the FastAPI app:
from starlette.requests import Request
@app.middleware("http")
async def add_user_to_request(request: Request, call_next):
try:
request.state.user = await get_current_user(request.headers.get("Authorization", ""))
except HTTPException:
request.state.user = None
response = await call_next(request)
return response
Deploying to Production
When you’re ready to go live, consider the following best practices:
- Use a real database: Replace the in‑memory
BOOKSlist with SQLAlchemy or Tortoise ORM. - Enable CORS: FastAPI’s
CORSMiddlewareprotects your GraphQL endpoint from unwanted origins. - Turn off debug mode: Set
debug=Falsein theGraphQLconstructor. - HTTPS & authentication: Serve behind a reverse proxy (e.g., Nginx) and enforce TLS.
- Performance tuning: Use
uvicorn --workers 4or an ASGI server likegunicornwithuvicorn.workers.UvicornWorker.
Common Pitfalls and How to Avoid Them
- Schema and resolver mismatch: Ensure every field declared in
.graphqlhas a corresponding resolver or a default property on the returned object. - Incorrect context handling: Remember that
info.contextis a dictionary; you must pass request‑specific data (e.g., user) explicitly. - Blocking I/O in resolvers: Use async functions or run blocking DB calls in a thread pool to keep the event loop responsive.
- Missing CORS headers: Without
CORSMiddleware, browsers will block GraphQL calls from different origins.
Extending the Tutorial: Subscriptions and Real‑Time Updates
If you need real‑time features, Ariadne supports GraphQL subscriptions over WebSockets. The basic steps are:
- Define a
Subscription
Leave a Reply