FastAPI 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, 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.
Prerequisites
Before we begin, ensure you have Python 3.9 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:
Next, install the main packages you'll need for GraphQL:
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 likewatchdogfor auto-reloading during development.
Create a new main.py file in your project root and populate it with the following foundational code:
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:
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:
Now, try executing this query to verify your setup:
You should receive a response like:
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.
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:
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:
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:
You will see output like this:
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:
Now when you run the same query, you'll see the author information populated for each book:
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:
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:
Now try this:
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 and FastAPI documentation.
Happy coding!