python

GraphQL API with Strawberry and FastAPI: Complete Production Guide 2024

Learn to build production-ready GraphQL APIs with Strawberry and FastAPI. Complete guide covering schemas, resolvers, authentication, and deployment best practices.

GraphQL API with Strawberry and FastAPI: Complete Production Guide 2024

Lately, I’ve been thinking about how we build APIs. Clients demand flexibility, while developers need maintainable systems. That’s why GraphQL caught my attention - it solves these problems elegantly. Combine it with Python’s modern tooling, and you get something special. Let me show you how I built production-ready GraphQL APIs using Strawberry and FastAPI. Stick around - this approach might change how you handle data delivery.

First, why Strawberry? Its type-safe approach with Python type hints feels natural. Compare it to older libraries:

# Traditional approach
import graphene

class Product(graphene.ObjectType):
    sku = graphene.String()
    price = graphene.Float()

# With Strawberry
import strawberry

@strawberry.type
class Product:
    sku: str
    price: float

Notice how the second version reads like standard Python? That’s why I chose it. Now, why pair it with FastAPI? Automatic OpenAPI docs, async support, and blistering performance. When clients request data, every millisecond matters.

Setting up is straightforward. Here’s my environment:

pip install fastapi uvicorn strawberry-graphql[debug-server]
pip install sqlalchemy asyncpg python-jose[cryptography]

My project structure keeps concerns separated:

app/
├── schemas/    # GraphQL types
├── resolvers/  # Data fetching logic
├── models/     # Database models
├── database.py # DB connections
└── main.py     # FastAPI entry point

For database operations, I use async SQLAlchemy. This snippet handles connections:

# database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/db"

engine = create_async_engine(DATABASE_URL)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession)

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

Now, defining schemas. Strawberry’s decorator syntax keeps things clean. Here’s a user type with relationships:

# schemas/user.py
import strawberry
from typing import List
from .post import Post

@strawberry.type
class User:
    id: int
    username: str
    email: str
    posts: List[Post]

But how do we fetch data? Resolvers handle that. Notice how I separate data fetching from type definitions:

# resolvers/user.py
import strawberry
from typing import List
from database import get_db
from models.user import User as UserModel

@strawberry.field
async def users() -> List[User]:
    async with get_db() as session:
        result = await session.execute(select(UserModel))
        return result.scalars().all()

Here’s where many hit performance issues. Without proper batching, you get N+1 queries. See this common mistake?

# Problematic approach
@strawberry.field
async def posts(self) -> List[Post]:
    # Queries database for EACH user
    return await get_posts(self.id)

The solution? DataLoaders. They batch requests automatically:

# dataloaders.py
from strawberry.dataloader import DataLoader

async def load_posts(user_ids):
    async with get_db() as session:
        stmt = select(Post).filter(Post.user_id.in_(user_ids))
        result = await session.execute(stmt)
        posts = result.scalars().all()
        return [p for user_id in user_ids 
                for p in posts if p.user_id == user_id]

post_loader = DataLoader(load_fn=load_posts)

For real-time updates, subscriptions are game-changers. With FastAPI’s WebSocket support:

import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

@strawberry.type
class Subscription:
    @strawberry.subscription
    async def new_posts(self) -> AsyncGenerator[Post, None]:
        async for post in post_stream():
            yield post

app = FastAPI()
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

Security can’t be an afterthought. Here’s my JWT authentication middleware:

# auth.py
from fastapi import Depends, HTTPException
from jose import jwt

async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload.get("sub")
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

Testing is crucial. I use pytest with HTTPX:

# test_api.py
import pytest
from httpx import AsyncClient

@pytest.mark.asyncio
async def test_user_query():
    query = "{ users { id username } }"
    async with AsyncClient(app=app) as client:
        response = await client.post("/graphql", json={"query": query})
        assert response.status_code == 200
        assert "users" in response.json()["data"]

For deployment, Docker simplifies everything:

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]

Production lessons I learned the hard way:

  • Always set query depth limits
  • Monitor resolver performance
  • Use Redis for caching frequent queries
  • Enable query logging in development

Why not REST or other GraphQL libraries? REST often over-fetches data. Compared to Graphene, Strawberry feels more Pythonic. Apollo-Server is great, but requires JavaScript.

Common pitfalls? Forgetting async context managers, ignoring N+1 problems, and weak validation. Always validate inputs:

@strawberry.input
class CreateUserInput:
    email: str
    password: str = strawberry.field(
        metadata={"validate": [Length(min=8, max=100)]}
    )

What surprised me most? How quickly teams adopted it. Frontend developers love specifying exact data needs. Backend teams appreciate the strict typing.

This approach scales beautifully. We handle thousands of requests per second with proper caching and load balancing. The key is starting with solid foundations - async I/O, batching, and clear schemas.

I’m curious - have you tried GraphQL in production? What challenges did you face? Share your experiences below. If this guide helped you, pass it along to others building modern APIs. Your thoughts and questions make these explorations valuable - comment away!

Keywords: GraphQL FastAPI tutorial, Strawberry GraphQL Python, GraphQL API development, FastAPI GraphQL integration, Python GraphQL framework, GraphQL schema design, GraphQL resolvers implementation, FastAPI async database, GraphQL performance optimization, Production GraphQL deployment



Similar Posts
Blog Image
FastAPI Microservices: Event-Driven Architecture with Apache Kafka and Pydantic Complete Guide

Learn to build scalable event-driven microservices using FastAPI, Apache Kafka, and Pydantic. Master async messaging, event sourcing, and production deployment techniques.

Blog Image
Building Production-Ready Real-Time Apps with FastAPI WebSockets and Redis Pub/Sub Complete Guide

Learn to build scalable real-time apps with FastAPI WebSockets and Redis Pub/Sub. Complete guide with auth, scaling, and production best practices.

Blog Image
Building Event-Driven Microservices with FastAPI, Apache Kafka and Pydantic: Complete Developer Guide

Learn to build scalable event-driven microservices with FastAPI, Kafka & Pydantic. Complete guide with code examples, error handling & deployment.

Blog Image
Building Event-Driven Microservices: FastAPI, Redis Streams, and AsyncIO Complete Tutorial

Learn to build event-driven microservices with FastAPI, Redis Streams & AsyncIO. Master scalable architecture patterns, event sourcing, CQRS & production deployment.

Blog Image
Build Real-Time WebSocket Applications with FastAPI and Redis for Instant Messaging Systems

Learn to build scalable real-time WebSocket apps with FastAPI and Redis Pub/Sub. Complete tutorial with authentication, chat rooms, and deployment tips.

Blog Image
Build Type-Safe Event-Driven Systems with Python: Pydantic, asyncio, and Redis Pub/Sub Complete Guide

Learn to build scalable, type-safe event-driven systems in Python using Pydantic, asyncio, and Redis Pub/Sub. Master async event processing & error handling.