Python Fastapi Graphql Integration Tutorial

Written by

in

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:

  1. Ariadne – Schema‑first approach, excellent for developers who prefer SDL (Schema Definition Language).
  2. Strawberry – Code‑first, leverages Python type hints to generate the schema automatically.
  3. 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 BOOKS list with SQLAlchemy or Tortoise ORM.
  • Enable CORS: FastAPI’s CORSMiddleware protects your GraphQL endpoint from unwanted origins.
  • Turn off debug mode: Set debug=False in the GraphQL constructor.
  • HTTPS & authentication: Serve behind a reverse proxy (e.g., Nginx) and enforce TLS.
  • Performance tuning: Use uvicorn --workers 4 or an ASGI server like gunicorn with uvicorn.workers.UvicornWorker.

Common Pitfalls and How to Avoid Them

  1. Schema and resolver mismatch: Ensure every field declared in .graphql has a corresponding resolver or a default property on the returned object.
  2. Incorrect context handling: Remember that info.context is a dictionary; you must pass request‑specific data (e.g., user) explicitly.
  3. Blocking I/O in resolvers: Use async functions or run blocking DB calls in a thread pool to keep the event loop responsive.
  4. 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

Comments

Leave a Reply

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