Python Fastapi Graphql Integration Tutorial

Written by

in

Welcome to the ultimate guide on integrating GraphQL with Python’s FastAPI! Whether you’re a seasoned backend developer or just starting out, this tutorial will walk you through every step— from setting up a clean environment to deploying a production‑ready GraphQL API. By the end, you’ll have a fast, type‑safe, and fully documented GraphQL endpoint that leverages FastAPI’s asynchronous power, making your services both scalable and developer‑friendly.

Why Combine FastAPI and GraphQL?

FastAPI has earned a reputation for lightning‑fast performance, automatic OpenAPI documentation, and seamless async support. GraphQL, on the other hand, offers:

  • Client‑driven queries: fetch exactly what you need, no over‑fetching.
  • Strong typing: schema definitions act as a contract between client and server.
  • Single endpoint: simplify routing and versioning.

When you pair FastAPI’s speed with GraphQL’s flexibility, you get an API that’s both performant and easy to evolve. This combination is perfect for micro‑services, mobile backends, and data‑intensive applications.

Prerequisites and Environment Setup

System Requirements

  • Python 3.9 or newer
  • Virtual environment tool (venv, poetry, or conda)
  • Basic knowledge of FastAPI and GraphQL concepts

Step‑by‑Step Installation

  1. Create and activate a virtual environment:
    python -m venv venv
    source venv/bin/activate  # On Windows use `venv\Scripts\activate`
  2. Install FastAPI, Uvicorn, and Strawberry (a modern Python GraphQL library):
    pip install fastapi uvicorn strawberry-graphql[fastapi]
  3. Optionally, install python-multipart if you plan to handle file uploads:
    pip install python-multipart

Creating a Basic FastAPI Application

First, let’s spin up a minimal FastAPI app to confirm everything works.

# app.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
async def health_check():
    return {"status": "OK"}

Run the server with:

uvicorn app:app --reload

Visit http://127.0.0.1:8000/health and you should see {"status":"OK"}. The --reload flag enables hot‑reloading during development.

Defining a GraphQL Schema with Strawberry

Strawberry uses Python type hints to generate a GraphQL schema, keeping your code DRY and type‑safe.

Step 1: Create Data Models

# models.py
import strawberry
from typing import List

@strawberry.type
class Book:
    id: int
    title: str
    author: str
    pages: int

# In‑memory "database"
BOOKS: List[Book] = [
    Book(id=1, title="1984", author="George Orwell", pages=328),
    Book(id=2, title="Brave New World", author="Aldous Huxley", pages=268),
]

Step 2: Write Query and Mutation Resolvers

# schema.py
import strawberry
from typing import List, Optional
from models import Book, BOOKS

@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> List[Book]:
        """Return all books."""
        return BOOKS

    @strawberry.field
    def book(self, id: int) -> Optional[Book]:
        """Find a single book by its ID."""
        return next((b for b in BOOKS if b.id == id), None)

@strawberry.type
class Mutation:
    @strawberry.mutation
    def add_book(self, title: str, author: str, pages: int) -> Book:
        """Create a new book and return it."""
        new_id = max(b.id for b in BOOKS) + 1 if BOOKS else 1
        new_book = Book(id=new_id, title=title, author=author, pages=pages)
        BOOKS.append(new_book)
        return new_book

schema = strawberry.Schema(query=Query, mutation=Mutation)

Mounting the GraphQL Endpoint in FastAPI

Strawberry provides a ready‑to‑use FastAPI integration. Update app.py as follows:

# app.py
from fastapi import FastAPI
import strawberry.fastapi
from schema import schema

app = FastAPI()

# Health endpoint (kept from earlier)
@app.get("/health")
async def health_check():
    return {"status": "OK"}

# Mount GraphQL at /graphql
graphql_app = strawberry.fastapi.GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

Restart the server and navigate to http://127.0.0.1:8000/graphql. Strawberry automatically serves GraphiQL, an interactive UI where you can test queries like:

query {
  books {
    id
    title
    author
  }
}

Handling Mutations and Variables

To add a new book, use the addBook mutation. GraphiQL lets you pass variables for cleaner queries:

mutation AddBook($title: String!, $author: String!, $pages: Int!) {
  addBook(title: $title, author: $author, pages: $pages) {
    id
    title
    author
    pages
  }
}

Variables JSON:

{
  "title": "Fahrenheit 451",
  "author": "Ray Bradbury",
  "pages": 256
}

Advanced Topics

Authentication Middleware

Secure your GraphQL endpoint by adding a simple dependency that checks an Authorization header.

# auth.py
from fastapi import Header, HTTPException

async def get_current_user(authorization: str = Header(None)):
    if authorization != "Bearer secret-token":
        raise HTTPException(status_code=401, detail="Invalid token")
    return {"username": "demo_user"}

Inject the dependency into the router:

# app.py (additional lines)
from fastapi import Depends
from auth import get_current_user

graphql_app = strawberry.fastapi.GraphQLRouter(
    schema,
    context_getter=lambda request: {"user": request.state.user}
)

@app.middleware("http")
async def add_user_to_state(request, call_next):
    request.state.user = await get_current_user(
        authorization=request.headers.get("Authorization")
    )
    response = await call_next(request)
    return response

Now each resolver can access info.context["user"] to enforce permissions.

Error Handling and Validation

  • Raise strawberry.exceptions.GraphQLError for custom GraphQL errors.
  • Use Pydantic models for input validation inside mutations.
# schema.py (excerpt)
import strawberry
from pydantic import BaseModel, validator

class BookInput(BaseModel):
    title: str
    author: str
    pages: int

    @validator('pages')
    def positive_pages(cls, v):
        if v <= 0:
            raise ValueError('Pages must be a positive integer')
        return v

@strawberry.type
class Mutation:
    @strawberry.mutation
    def add_book(self, input: BookInput) -> Book:
        data = input.dict()
        # ... same logic as before

Performance Tips

  • Use async resolvers: Declare resolver functions with async def to leverage FastAPI’s non‑blocking I/O.
  • Batch data fetching: Implement DataLoader patterns to avoid N+1 query problems.
  • Cache static queries: Store frequently requested results in Redis or in‑memory caches.

Testing Your GraphQL API

FastAPI’s TestClient works seamlessly with Strawberry. Below is a minimal test suite using pytest:

# test_graphql.py
from fastapi.testclient import TestClient
from app import app

client = TestClient(app)

def test_books_query():
    query = """
    query {
        books {
            id
            title
        }
    }
    """
    response = client.post("/graphql", json={"query": query})
    assert response.status_code == 200
    data = response.json()["data"]
    assert "books" in data
    assert isinstance(data["books"], list)

Deploying to Production

When you’re ready to go live, consider these best practices:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *