Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected
Tutorial

The Complete Guide to Setting Up GraphQL in Your Project

A step-by-step guide to building a GraphQL API with Python and Strawberry, from schema design to database integration, authentication, and optimization.

July 2026 12 min read 1 views 0 hearts

GraphQL is one of those technologies that sounds complicated until you actually try it. I remember when I first heard about it at PythonSkillset — everyone was talking about how it would replace REST, and I thought, "Great, another thing I need to learn." But once I set it up in a real project, I realized it's actually simpler than it seems. Let me walk you through the entire process, from zero to a working GraphQL API.

Why GraphQL Instead of REST?

Before we dive into setup, let's talk about why you'd want GraphQL in the first place. With REST, you often end up with either too much data (over-fetching) or not enough (under-fetching). For example, if you have a user profile endpoint that returns 20 fields but you only need the name and email, you're still getting all that extra data. GraphQL lets you ask for exactly what you need.

At PythonSkillset, we switched one of our internal tools from REST to GraphQL and saw our API response sizes drop by about 40%. That's a real difference when you're serving thousands of requests.

What You'll Need

Before we start, make sure you have: - Python 3.7 or higher installed - A basic understanding of Python classes and functions - pip (Python's package manager)

Step 1: Choose Your GraphQL Library

For Python, the two main options are Graphene and Strawberry. I prefer Strawberry because it uses Python type hints, which makes the code cleaner and more readable. But Graphene is also solid and has been around longer.

For this guide, we'll use Strawberry because it feels more "Pythonic" and integrates well with modern Python features.

pip install strawberry-graphql

Step 2: Define Your Data Types

The first thing you need is a schema. Think of this as a blueprint for your API. Let's say we're building a simple blog system.

import strawberry

@strawberry.type
class Author:
    id: int
    name: str
    email: str

@strawberry.type
class Post:
    id: int
    title: str
    content: str
    author: Author
    published_at: str

Notice how clean this looks. Each class becomes a GraphQL type automatically. The @strawberry.type decorator tells Strawberry to treat this as a GraphQL object type.

Step 3: Create Your Query Resolvers

Now we need to tell GraphQL how to actually get the data. This is where resolvers come in. A resolver is just a function that returns data for a specific field.

@strawberry.type
class Query:
    @strawberry.field
    def get_author(self, id: int) -> Author:
        # In a real app, you'd query your database here
        return Author(id=id, name="Jane Doe", email="jane@example.com")

    @strawberry.field
    def get_posts(self) -> list[Post]:
        # Simulating a database query
        author = Author(id=1, name="Jane Doe", email="jane@example.com")
        return [
            Post(id=1, title="First Post", content="Hello world", author=author, published_at="2024-01-15"),
            Post(id=2, title="Second Post", content="GraphQL is cool", author=author, published_at="2024-01-20")
        ]

The @strawberry.field decorator marks each method as a queryable field. The return type annotations tell GraphQL what shape the data will have.

Step 4: Build the Schema

Now we connect everything together:

schema = strawberry.Schema(query=Query)

That's it. One line. The schema object now knows about all your types and queries.

Step 5: Set Up the Server

You need a way to serve your GraphQL API. FastAPI works great with Strawberry:

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

app = FastAPI()
graphql_app = GraphQLRouter(schema)

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

Run this with uvicorn:

uvicorn main:app --reload

Now visit http://localhost:8000/graphql and you'll see the GraphQL playground where you can test your queries.

Step 6: Write Your First Query

Open the playground and try this:

{
  getPosts {
    title
    content
    author {
      name
    }
  }
}

You'll get back exactly the fields you asked for — no more, no less. That's the beauty of GraphQL.

Step 7: Add Mutations (Writing Data)

Queries are for reading data. For creating, updating, or deleting, you need mutations. Here's how:

@strawberry.type
class Mutation:
    @strawberry.mutation
    def create_post(self, title: str, content: str, author_id: int) -> Post:
        # In a real app, save to database
        author = Author(id=author_id, name="Jane Doe", email="jane@example.com")
        return Post(id=3, title=title, content=content, author=author, published_at="2024-02-01")

Add it to your schema:

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

Now you can run mutations like:

mutation {
  createPost(title: "New Post", content: "My content", authorId: 1) {
    id
    title
  }
}

Step 8: Handle Real Data with a Database

In a real project, you won't return hardcoded data. Here's how to connect to a SQLite database using SQLAlchemy:

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
engine = create_engine("sqlite:///blog.db")
SessionLocal = sessionmaker(bind=engine)

class AuthorModel(Base):
    __tablename__ = "authors"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

class PostModel(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String)
    content = Column(String)
    author_id = Column(Integer)

Then update your resolvers to query the database:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@strawberry.type
class Query:
    @strawberry.field
    def get_posts(self) -> list[Post]:
        db = next(get_db())
        posts = db.query(PostModel).all()
        return [
            Post(
                id=post.id,
                title=post.title,
                content=post.content,
                author=Author(id=post.author_id, name="Jane", email="jane@example.com"),
                published_at=str(post.published_at)
            )
            for post in posts
        ]

Step 8: Handle Errors Gracefully

GraphQL has a built-in error handling system. Instead of crashing your whole API when something goes wrong, you can return partial results with error messages:

from strawberry.types import Info

@strawberry.type
class Query:
    @strawberry.field
    def get_author(self, id: int, info: Info) -> Author | None:
        try:
            db = next(get_db())
            author = db.query(AuthorModel).filter(AuthorModel.id == id).first()
            if not author:
                raise Exception(f"Author with id {id} not found")
            return Author(id=author.id, name=author.name, email=author.email)
        except Exception as e:
            # GraphQL will handle this gracefully
            raise e

Step 9: Add Authentication

Most real projects need authentication. Here's a simple way to add it:

from strawberry.permission import BasePermission
from fastapi import Request

class IsAuthenticated(BasePermission):
    message = "You need to be logged in"

    def has_permission(self, source, info: Info, **kwargs) -> bool:
        request: Request = info.context["request"]
        # Check for auth token in headers
        auth_header = request.headers.get("Authorization")
        if not auth_header:
            return False
        # Validate token (simplified example)
        return auth_header.startswith("Bearer ")

@strawberry.type
class Query:
    @strawberry.field(permission_classes=[IsAuthenticated])
    def get_private_data(self) -> str:
        return "This is secret data"

Step 10: Optimize with DataLoaders

One common problem with GraphQL is the N+1 query problem. If you have a list of posts and each post needs its author, you might end up querying the database once for the posts and then once for each author. DataLoaders batch these requests:

from strawberry.dataloader import DataLoader
from collections import defaultdict

class AuthorLoader(DataLoader):
    async def load_keys(self, keys):
        # Batch load all authors at once
        db = next(get_db())
        authors = db.query(AuthorModel).filter(AuthorModel.id.in_(keys)).all()
        author_map = {author.id: author for author in authors}
        return [author_map.get(key) for key in keys]

@strawberry.type
class Post:
    @strawberry.field
    async def author(self) -> Author:
        loader = AuthorLoader()
        author_data = await loader.load(self.author_id)
        return Author(id=author_data.id, name=author_data.name, email=author_data.email)

Step 10: Test Your API

Don't skip testing. GraphQL makes testing easier because you can test individual resolvers:

import pytest
from strawberry.schema.config import StrawberryConfig

def test_get_posts():
    # Create a test schema
    test_schema = strawberry.Schema(query=Query)

    # Execute a query
    result = test_schema.execute_sync("""
        query {
            getPosts {
                title
                content
            }
        }
    """)

    assert result.data is not None
    assert len(result.data["getPosts"]) > 0
    assert "title" in result.data["getPosts"][0]

Common Pitfalls to Avoid

  1. Don't expose your database models directly — Always create separate GraphQL types. This gives you control over what gets exposed.

  2. Watch out for circular references — If Post has an Author and Author has Posts, you'll get infinite loops. Use strawberry.lazy to handle this.

  3. Don't forget about pagination — Without it, a query for all posts could return thousands of results. Use strawberry.relay for cursor-based pagination.

When GraphQL Might Not Be Right

GraphQL isn't always the answer. If you have a simple API with a few endpoints, REST might be simpler. Also, GraphQL caching is more complex than REST caching. For public APIs where you want to leverage CDN caching, REST might be better.

But for complex applications with multiple data sources and varying client needs, GraphQL shines. At PythonSkillset, we use it for our dashboard where different teams need different data views.

Next Steps

Once you have the basics working, explore: - Subscriptions for real-time data - Federation for microservices - Apollo Studio for monitoring

The best way to learn is to build something real. Start with a small project — maybe a todo list or a simple blog — and expand from there. You'll quickly see why GraphQL has become so popular in modern web development.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.