FastAPI has quickly become a favorite among Python developers for building high‑performance APIs, thanks to its async capabilities, automatic documentation, and developer‑friendly design. But what if you need the flexibility of GraphQL alongside FastAPI’s speed? In this tutorial, we’ll walk through a step‑by‑step integration of GraphQL into a FastAPI project, covering everything from setting up the environment to defining schemas, resolvers, and testing your endpoint. By the end, you’ll have a production‑ready FastAPI app that serves GraphQL queries and mutations, ready to boost your API’s efficiency and developer experience.
Why Combine FastAPI with GraphQL?
Before diving into code, let’s explore the key benefits of pairing FastAPI with GraphQL:
- Selective Data Retrieval: Clients request exactly the fields they need, reducing over‑fetching and under‑fetching.
- Single Endpoint: One flexible endpoint replaces multiple REST routes, simplifying versioning and maintenance.
- Strong Type System: GraphQL’s schema definition provides clear contracts, while FastAPI’s Pydantic models ensure data validation.
- Async‑First Performance: FastAPI’s async support pairs perfectly with GraphQL resolvers that may call async database drivers.
Prerequisites
Make sure you have the following before starting:
- Python 3.9+ installed.
- Basic knowledge of FastAPI and GraphQL concepts.
- A virtual environment (venv or Conda) to keep dependencies isolated.
Step 1: Set Up the Project Structure
First, create a clean directory layout. This structure keeps your code organized and scales well as the project grows.
fastapi-graphql-demo/
├─ app/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ schema.py
│ └─ models.py
├─ requirements.txt
└─ README.md
Step 2: Install Required Packages
We’ll use fastapi, uvicorn for the ASGI server, strawberry-graphql as a modern GraphQL library, and pydantic for data validation.
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install fastapi uvicorn strawberry-graphql[fastapi] pydantic
pip freeze > requirements.txt
Step 3: Define Pydantic Models
In app/models.py, create the data models that will back your GraphQL types. Using Pydantic ensures automatic validation and serialization.
from pydantic import BaseModel
from typing import List, Optional
class Book(BaseModel):
id: int
title: str
author: str
pages: Optional[int] = None
class Author(BaseModel):
id: int
name: str
books: List[Book] = []
Step 4: Build the GraphQL Schema with Strawberry
Strawberry lets you define GraphQL types using Python dataclasses, which feels natural for FastAPI developers.
import strawberry
from typing import List
from .models import Book as BookModel, Author as AuthorModel
@strawberry.type
class Book:
id: int
title: str
author: str
pages: int | None = None
@strawberry.type
class Author:
id: int
name: str
books: List[Book]
# In‑memory data store for demo purposes
books_db = [
BookModel(id=1, title="FastAPI in Action", author="John Doe", pages=320),
BookModel(id=2, title="GraphQL Essentials", author="Jane Smith", pages=210),
]
authors_db = [
AuthorModel(id=1, name="John Doe", books=[books_db[0]]),
AuthorModel(id=2, name="Jane Smith", books=[books_db[1]]),
]
@strawberry.type
class Query:
@strawberry.field
def books(self) -> List[Book]:
return [Book(**b.dict()) for b in books_db]
@strawberry.field
def authors(self) -> List[Author]:
return [Author(id=a.id, name=a.name, books=[Book(**b.dict()) for b in a.books]) for a in authors_db]
@strawberry.type
class Mutation:
@strawberry.mutation
def add_book(self, title: str, author: str, pages: int | None = None) -> Book:
new_id = max(b.id for b in books_db) + 1
new_book = BookModel(id=new_id, title=title, author=author, pages=pages)
books_db.append(new_book)
# Update author relationship
for a in authors_db:
if a.name == author:
a.books.append(new_book)
break
else:
authors_db.append(AuthorModel(id=len(authors_db)+1, name=author, books=[new_book]))
return Book(**new_book.dict())
schema = strawberry.Schema(query=Query, mutation=Mutation)
Step 5: Wire GraphQL into FastAPI
FastAPI’s APIRouter makes it trivial to mount the Strawberry GraphQL view.
# app/main.py
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
from .schema import schema
app = FastAPI(
title="FastAPI + GraphQL Demo",
description="A simple tutorial showing how to integrate GraphQL with FastAPI using Strawberry.",
version="1.0.0",
)
graphql_app = GraphQLRouter(schema, graphiql=True) # Enable GraphiQL UI for testing
app.include_router(graphql_app, prefix="/graphql")
# Optional: Add a health‑check endpoint
@app.get("/health")
async def health_check():
return {"status": "ok"}
Step 6: Run the Application
Start the server with uvicorn. The GraphQL playground will be available at http://localhost:8000/graphql.
uvicorn app.main:app --reload
Testing a Query
Open the GraphiQL UI and run:
query {
books {
id
title
author
pages
}
}
You should see a JSON response containing the two sample books.
Testing a Mutation
Add a new book using the following mutation:
mutation {
addBook(title: "Async Python", author: "Alice", pages: 400) {
id
title
author
pages
}
}
The new entry will be persisted in the in‑memory list, and subsequent queries will return it.
Step 7: Adding Database Integration (Optional)
For production use, replace the in‑memory lists with an async ORM such as SQLModel or SQLAlchemy 2.0. Below is a quick outline using SQLModel:
# Install extra dependency
pip install sqlmodel[async]
# models.py (extended)
from sqlmodel import SQLModel, Field, Relationship
from typing import List, Optional
class Book(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
author: str
pages: Optional[int] = None
author_id: Optional[int] = Field(default=None, foreign_key="author.id")
class Author(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
books: List[Book] = Relationship(back_populates="author")
In your resolvers, use await session.exec(...) to fetch data asynchronously. The rest of the GraphQL schema remains unchanged, demonstrating how Strawberry cleanly separates schema definition from data fetching logic.
Best Practices for FastAPI‑GraphQL Projects
- Schema First Design: Draft your GraphQL schema before writing resolvers; this ensures a clear contract.
- Use Data Loaders: Prevent the N+1 query problem by batching database calls with
strawberry.dataloaderor external libraries. - Enable CORS: If your frontend runs on a different domain, add
fastapi.middleware.cors.CORSMiddlewareto allow cross‑origin requests. - Secure the Endpoint: Protect the GraphQL route with OAuth2, API keys, or JWT authentication using FastAPI’s dependency injection system.
- Version Your API: Although GraphQL encourages a single evolving endpoint, you can still version the underlying FastAPI app (e.g.,
/v1/graphql) for major breaking changes.
Deploying to Production
When you’re ready to go live, consider the following deployment checklist:
- Use an ASGI server like
uvicornorhypercornbehind a reverse proxy (NGINX or Traefik). - Enable HTTP/2 and TLS for secure, high‑throughput connections.
- Configure logging and monitoring (e.g.,
structlog, Prometheus, Grafana). - Set up automated tests for both GraphQL queries and FastAPI routes using
pytestandhttpx. - Scale horizontally with containers (Docker) or serverless platforms that support ASGI.
Conclusion
Integrating GraphQL with FastAPI gives you the best of both worlds: the speed and
Leave a Reply