# Using GraphQL with Python and FastAPI

[FastAPI](https://fastapi.tiangolo.com/) is a web framework that has transformed Python API development through its exceptional performance, intuitive design, and comprehensive type safety features. 

When combined with [GraphQL](https://graphql.org/), FastAPI creates an incredibly powerful platform for building modern, flexible APIs that can adapt to evolving client requirements without breaking existing implementations.


This comprehensive tutorial will guide you through building production-ready GraphQL APIs using FastAPI and [Strawberry GraphQL](https://strawberry.rocks/).

[ad-logs]

## Prerequisites

Before we begin, ensure you have [Python 3.9](https://www.python.org/downloads/) or newer installed. This guide also assumes you're comfortable with Python functions, decorators, and basic web APIs.


## Setting up your FastAPI GraphQL service

To get the most out of this tutorial, it’s a good idea to set up a fresh FastAPI project so you can follow along and try things out yourself. 

Start by creating a new folder for your project and setting up a virtual environment:

```command
mkdir fastapi-graphql-api && cd fastapi-graphql-api
```

```command
python3 -m venv venv
```
```command
source venv/bin/activate
```

Next, install the main packages you'll need for GraphQL:

```command
pip install fastapi "strawberry-graphql[fastapi]" "uvicorn[standard]"
```

Here’s a quick breakdown of what each package does:

* `fastapi`: The main web framework we’ll use to build the API.
* `strawberry-graphql[fastapi]`: This adds GraphQL support to FastAPI using Strawberry. It includes tools to define your schema, queries, and mutations.
* `uvicorn[standard]`: A fast ASGI server that runs your FastAPI app. The `[standard]` part includes helpful extras like `watchdog` for auto-reloading during development.

Create a new `main.py` file in your project root and populate it with the following foundational code:

```python
[label main.py]
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

@strawberry.type
class User:
    name: str
    age: int

@strawberry.type
class Query:
    @strawberry.field
    def user(self) -> User:
        return User(name="Patrick", age=100)
    
    @strawberry.field
    def hello(self, name: str = "World") -> str:
        return f"Hello, {name}! Welcome to FastAPI + GraphQL"

schema = strawberry.Schema(query=Query)
graphql_app = GraphQLRouter(schema)

app = FastAPI(title="FastAPI GraphQL API")
app.include_router(graphql_app, prefix="/graphql")
```


This code sets up a basic GraphQL schema using Strawberry, following the official FastAPI pattern. The `Query` class defines two fields: `hello` (which takes an optional name) and `user` (which returns a user object). The `@strawberry.type` and `@strawberry.field` decorators turn regular Python classes and methods into GraphQL types and fields.

The setup uses FastAPI’s preferred method: create a Strawberry schema, wrap it in a `GraphQLRouter`, and include it in your app with a prefix. This lets you mix GraphQL and regular FastAPI routes in the same project.


We'll explore various ways to customize the schema and add more complex functionality later, but for 
now, let's test the basic implementation by launching your GraphQL server using the following command:

```command
uvicorn main:app --reload
```

```text
[output]

INFO:     Will watch for changes in these directories: ['/Users/stanley/fastapi-graphql-api']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [71473] using WatchFiles
INFO:     Started server process [71477]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
```

Navigate to `http://localhost:8000/graphql` in your browser to access the GraphiQL interface. You should see an interactive GraphQL IDE that allows you to explore and test your API:

![Screenshot of the GraphiQL interface](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/b3e3674b-e24b-4e59-87aa-ecaa6c888400/lg2x =3248x1996)

Now, try executing this query to verify your setup:


```graphql
{
  user {
    name
    age
  }
}
```

You should receive a response like:

```json
[output]
{
  "data": {
    "user": {
      "name": "Patrick",
      "age": 100
    }
  }
}
```

![Screenshot of the GraphiQL interface with responses](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/ff993b5e-2784-4155-58f2-45b75f783e00/lg1x =3248x1996)

One of the first things you'll notice about the GraphiQL interface is how developer-friendly it is. It offers features like auto-complete, syntax highlighting, and a built-in way to explore your API's schema. 

These tools make it easy to test different queries, experiment with your API, and see exactly what data is available.


## Designing GraphQL schemas

A GraphQL schema is the core contract between your API and its users. It defines all available operations, data types, and how they relate. Unlike REST, which relies on multiple endpoints, GraphQL uses a single, well-structured schema to describe the entire API.

Schemas are made up of types, fields, arguments, and operations like queries and mutations. Understanding these pieces is key to designing clean, scalable APIs.

Let’s create a more realistic schema. Start by creating a new file called `models.py` to define our data types.

```python
[label models.py]
from typing import List, Optional
from datetime import datetime
import strawberry

@strawberry.type
class Author:
    id: int
    name: str
    email: str
    bio: Optional[str] = None
    created_at: datetime
    books: List["Book"] = strawberry.field(default_factory=list)

@strawberry.type
class Book:
    id: int
    title: str
    isbn: str
    published_year: int
    page_count: int
    author_id: int
    author: Optional[Author] = None
```

These types of definitions highlight key GraphQL concepts. `Author` and `Book` show how to create object types with scalar fields (like `int` and `str`), optional fields, and relationships to other types. The use of `"Book"` as a forward reference in the `Author` type lets us handle circular references between types.

We're also using Python's type hints throughout. Strawberry reads these hints to generate the GraphQL schema automatically. This keeps the code clean and easy to follow, while also providing helpful editor support and type-checking.

Next, let’s define input types for mutations. Input types are used to pass structured arguments into queries and mutations. Add the following to your `models.py` file:

```python
[label models.py]
...
@strawberry.input
class CreateAuthorInput:
    name: str
    email: str
    bio: Optional[str] = None

@strawberry.input
class CreateBookInput:
    title: str
    isbn: str
    published_year: int
    page_count: int
    author_id: int

@strawberry.input
class UpdateBookInput:
    title: Optional[str] = None
    isbn: Optional[str] = None
    published_year: Optional[int] = None
    page_count: Optional[int] = None
```

Input types offer several advantages over using individual arguments: they group related parameters together, make mutations more readable, and facilitate easier validation and transformation. 

The `UpdateBookInput` type demonstrates how to create partial update inputs where all fields are optional.

Update your `main.py` file to include these new types and provide some sample data to work with:

```python
[label main.py]
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
[highlight]
from typing import List, Optional
from models import Author, Book, CreateBookInput, CreateAuthorInput
from datetime import datetime

# Sample data for demonstration
authors_db = [
    Author(
        id=1,
        name="George Orwell",
        email="george@orwell.com",
        bio="English novelist and critic",
        created_at=datetime(2020, 1, 15),
    ),
    Author(
        id=2,
        name="Ray Bradbury",
        email="ray@bradbury.com",
        bio="American author and screenwriter",
        created_at=datetime(2020, 3, 10),
    ),
]

books_db = [
    Book(
        id=1,
        title="1984",
        isbn="978-0-452-28423-4",
        published_year=1949,
        page_count=328,
        author_id=1,
    ),
    Book(
        id=2,
        title="Fahrenheit 451",
        isbn="978-1-451-67331-9",
        published_year=1953,
        page_count=194,
        author_id=2,
    ),
]
[/highlight]

@strawberry.type
class User:
    name: str
    age: int

@strawberry.type
class Query:
    @strawberry.field
    def user(self) -> User:
        return User(name="Patrick", age=100)
    
    @strawberry.field
    def hello(self, name: str = "World") -> str:
        return f"Hello, {name}! Welcome to FastAPI + GraphQL"
    
    [highlight]
    @strawberry.field
    def books(self) -> List[Book]:
        return books_db
    
    @strawberry.field
    def book(self, id: int) -> Optional[Book]:
        return next((book for book in books_db if book.id == id), None)
    
    @strawberry.field
    def authors(self) -> List[Author]:
        return authors_db
    
    @strawberry.field
    def author(self, id: int) -> Optional[Author]:
        return next((author for author in authors_db if author.id == id), None)
    [/highlight]

schema = strawberry.Schema(query=Query)
graphql_app = GraphQLRouter(schema)

app = FastAPI(title="FastAPI GraphQL API")
app.include_router(graphql_app, prefix="/graphql")
```

This enhanced schema demonstrates several important query patterns. The `books` and `authors` fields return lists of items, while `book` and `author` provide single-item lookups by ID. The use of `Optional` return types indicates that these fields might return `None` if no matching item is found.

Test your enhanced schema with this query that demonstrates GraphQL's ability to fetch related data in a single request:

```graphql
{
  books {
    id
    title
    publishedYear
    pageCount
    author {
      name
      bio
    }
  }
}
```

You will see output like this:

```text
[output]
{
  "data": {
    "books": [
      {
        "id": 1,
        "title": "1984",
        "publishedYear": 1949,
        "pageCount": 328,
        "author": null
      },
      {
        "id": 2,
        "title": "Fahrenheit 451",
        "publishedYear": 1953,
        "pageCount": 194,
        "author": null
      }
    ]
  }
}
```
![Screenshot of the GraphQL output](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/6b80121d-f74e-4e00-9520-b91ba5868b00/lg2x =3248x1996)

However, you'll notice that the `author` field in the book results returns `null`. This is because we haven't implemented the relationship resolution yet. Let's add resolver methods to handle these relationships:

```python
[label models.py]
from typing import List, Optional
from datetime import datetime
import strawberry

@strawberry.type
class Author:
    id: int
    ..
    books: List["Book"] = strawberry.field(default_factory=list)
    
    [highlight]
    @strawberry.field
    def books(self) -> List["Book"]:
        # Import here to avoid circular import
        from main import books_db
        return [book for book in books_db if book.author_id == self.id]
    [/highlight]

@strawberry.type
class Book:
    id: int
    ...
    author: Optional[Author] = None
    
    [highlight]
    @strawberry.field
    def author(self) -> Optional[Author]:
        # Import here to avoid circular import
        from main import authors_db
        return next((author for author in authors_db if author.id == self.author_id), None)
    [/highlight]

@strawberry.input
class CreateAuthorInput:
   ...
```
Now when you run the same query, you'll see the author information populated for each book:

![Screenshot in the GraphQL interface showing the author information populated](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/a80c7e07-b5a1-46fe-3899-f314235e2c00/md2x =3248x1996)

This demonstrates GraphQL's power to traverse relationships and return exactly the data requested by the client.


## Implementing mutations for data modification

While queries handle data retrieval, mutations manage data modifications such as creating, updating, or deleting resources. GraphQL mutations provide a structured approach to state changes while maintaining the same type safety and flexibility as queries. They follow a predictable pattern: accept input parameters, perform the operation, and return the modified data along with any relevant metadata.

Mutations are particularly powerful because they can return complex objects that include both the modified data and additional context like validation errors, success indicators, or related information that changed as a result of the operation.

Let's add comprehensive mutation capabilities to our API. First, add a `Mutation` class to your `main.py` file:

```python
[label main.py]
 ...
@strawberry.type
class Query:
    @strawberry.field
    def user(self) -> User:
        return User(name="Patrick", age=100)
    
    ...
    
    @strawberry.field
    def author(self, id: int) -> Optional[Author]:
        return next((author for author in authors_db if author.id == id), None)

[highlight]
@strawberry.type
class Mutation:
    @strawberry.mutation
    def create_author(self, input: CreateAuthorInput) -> Author:
        new_id = max([author.id for author in authors_db], default=0) + 1
        new_author = Author(
            id=new_id,
            name=input.name,
            email=input.email,
            bio=input.bio,
            created_at=datetime.now()
        )
        authors_db.append(new_author)
        return new_author
    
    @strawberry.mutation
    def create_book(self, input: CreateBookInput) -> Book:
        # Validate that the author exists
        author = next((a for a in authors_db if a.id == input.author_id), None)
        if not author:
            raise Exception(f"Author with ID {input.author_id} not found")
        
        new_id = max([book.id for book in books_db], default=0) + 1
        new_book = Book(
            id=new_id,
            title=input.title,
            isbn=input.isbn,
            published_year=input.published_year,
            page_count=input.page_count,
            author_id=input.author_id
        )
        books_db.append(new_book)
        return new_book
[/highlight]

[highlight]
schema = strawberry.Schema(query=Query, mutation=Mutation)
[/highlight]
graphql_app = GraphQLRouter(schema)

app = FastAPI(title="FastAPI GraphQL API")
app.include_router(graphql_app, prefix="/graphql")
```

The mutation resolvers demonstrate several important patterns. First, they accept input objects rather than individual parameters, which keeps the GraphQL schema clean and makes validation easier. Second, they perform validation before making changes - the `create_book` mutation verifies that the referenced author exists before creating the book.

Notice how we generate new IDs by finding the maximum existing ID and incrementing it. In a real application, you'd typically let your database handle ID generation, but this approach works well for our demonstration.

Test your mutations with these GraphQL operations:

```graphql
mutation {
  createAuthor(input: {
    name: "Isaac Asimov"
    email: "isaac@asimov.com"
    bio: "American science fiction writer"
  }) {
    id
    name
    email
    createdAt
  }
}
```
```text
[output]
{
  "data": {
    "createAuthor": {
      "id": 4,
      "name": "Isaac Asimov",
      "email": "isaac@asimov.com",
      "createdAt": "2025-06-17T13:44:41.006003"
    }
  }
}
```
![Screenshot of `createAuthor` mutation set](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/1bbe5003-b12a-4364-21a5-e400f7f6ff00/orig =3248x1996)

Now try this:

```graphql
mutation {
  createBook(input: {
    title: "Foundation"
    isbn: "978-0-553-29335-0"
    publishedYear: 1951
    pageCount: 244
    authorId: 3
  }) {
    id
    title
    isbn
    author {
      name
    }
  }
}
```

```text
[output]
{
  "data": {
    "createBook": {
      "id": 3,
      "title": "Foundation",
      "isbn": "978-0-553-29335-0",
      "author": {
        "name": "Isaac Asimov"
      }
    }
  }
}
```

![Screenshot of the GraphQL output](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/58f05e4a-6c9d-44fc-b6f3-84153abacd00/md2x =3248x1996)

The mutation system provides a clear interface for data modifications while maintaining the same type safety and introspection capabilities that make GraphQL queries so powerful. 

Clients can specify exactly what data they want returned after the mutation completes, which is particularly useful for updating UI components efficiently.

## Final thoughts

You’ve learned how to build a GraphQL API using FastAPI and Strawberry, including how to set up your project, define types, and create queries and mutations.

This approach offers a fast, flexible, and type-safe way to develop modern APIs. Next, you can add features like a database, authentication, or pagination.

For more details, visit the [Strawberry](https://strawberry.rocks/docs) and [FastAPI](https://fastapi.tiangolo.com/) documentation.

Happy coding!
