0Pricing
FastAPI Backend Development Bootcamp · 강의

타입, 쿼리 및 변형 정의

Strawberry로 타입이 지정된 GraphQL 스키마를 구축하고 공유 의존성을 사용하는 FastAPI 앱에 연결합니다.

타입, 쿼리 및 변형 정의은(는) CoddyKit의 무료 FastAPI Backend Development Bootcamp 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 FastAPI Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. FastAPI Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Strawberry for GraphQL on FastAPI

Strawberry is a code-first GraphQL library for Python that uses dataclasses and type hints to define your schema. Instead of writing GraphQL SDL by hand, you write plain Python classes and Strawberry derives the schema from them.

  • Code-first: the Python types ARE the source of truth — the SDL is generated.
  • Type-safe: standard type hints (int, str, list[str], Optional) map directly to GraphQL types.
  • ASGI-native: ships a router that mounts cleanly on a FastAPI app, sharing its event loop and dependency system.

In this lesson we build a typed schema (types, a Query, and a Mutation) and mount it on FastAPI with shared dependencies.

Defining an Object Type

A GraphQL object type is just a class decorated with @strawberry.type. Each annotated attribute becomes a field. Type hints determine the GraphQL field type: int becomes Int, str becomes String, and a non-optional field becomes non-null (!).

  • Use strawberry.ID for identifier fields — it serializes as a string but signals identity semantics.
  • Optional[...] (or X | None) makes a field nullable.
import strawberry
from typing import Optional


@strawberry.type
class Book:
    id: strawberry.ID
    title: str
    author: str
    pages: int
    summary: Optional[str] = None

The Query Root Type

Every GraphQL schema needs a Query root — the entry point for reads. You declare it as a @strawberry.type whose fields are resolved by methods decorated with @strawberry.field.

  • The method's return annotation defines the field's GraphQL type.
  • Method parameters (other than self) become GraphQL arguments.
  • Returning a list[Book] produces a non-null list of non-null books: [Book!]!.
import strawberry


@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> list[Book]:
        return [
            Book(id="1", title="Dune", author="Herbert", pages=412),
            Book(id="2", title="1984", author="Orwell", pages=328),
        ]

    @strawberry.field
    def book(self, id: strawberry.ID) -> Book | None:
        for b in self.books():
            if b.id == id:
                return b
        return None

Building the Schema

strawberry.Schema ties root types together. At minimum you pass query=Query; later you add mutation=Mutation. Building the schema validates your types and lets you print the generated SDL — a great sanity check.

Here is a fully standalone example: define a type, a query, build the schema, and execute a query synchronously with schema.execute_sync. No server or framework needed.

import strawberry


@strawberry.type
class Book:
    id: strawberry.ID
    title: str
    author: str


@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> list[Book]:
        return [Book(id="1", title="Dune", author="Herbert")]


schema = strawberry.Schema(query=Query)

result = schema.execute_sync("{ books { id title author } }")
print(result.errors)
print(result.data)

Field Arguments and Defaults

GraphQL arguments come straight from resolver parameters. A parameter with a default value becomes an optional argument; without a default it is required.

  • Use typing.Optional + a default to express a nullable, optional argument.
  • Strawberry coerces incoming argument values to the annotated Python type automatically.

Below, limit defaults to 10 and genre is an optional filter.

import strawberry
from typing import Optional


@strawberry.type
class Book:
    id: strawberry.ID
    title: str
    genre: str


LIBRARY = [
    Book(id="1", title="Dune", genre="scifi"),
    Book(id="2", title="It", genre="horror"),
]


@strawberry.type
class Query:
    @strawberry.field
    def books(self, limit: int = 10, genre: Optional[str] = None) -> list[Book]:
        items = LIBRARY if genre is None else [b for b in LIBRARY if b.genre == genre]
        return items[:limit]


schema = strawberry.Schema(query=Query)
print(schema.execute_sync('{ books(genre: "scifi") { title } }').data)

Input Types for Mutations

Mutations that accept structured data should use an input type: a class decorated with @strawberry.input. Input types are the GraphQL equivalent of a request body — they keep mutation signatures clean and self-documenting.

  • Fields without defaults are required input fields.
  • Reuse the same input across create/update flows by making fields optional where appropriate.
import strawberry
from typing import Optional


@strawberry.input
class AddBookInput:
    title: str
    author: str
    pages: Optional[int] = None

The Mutation Root Type

The Mutation root mirrors Query but expresses writes. Each method is a @strawberry.mutation. Convention: take an input type, perform the side effect, and return the created or updated object so the client can read fresh fields in one round trip.

This example keeps an in-memory store and returns the new Book. It is fully standalone and runnable.

import strawberry

_DB: list["Book"] = []


@strawberry.type
class Book:
    id: strawberry.ID
    title: str
    author: str


@strawberry.input
class AddBookInput:
    title: str
    author: str


@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> list[Book]:
        return _DB


@strawberry.type
class Mutation:
    @strawberry.mutation
    def add_book(self, data: AddBookInput) -> Book:
        book = Book(id=str(len(_DB) + 1), title=data.title, author=data.author)
        _DB.append(book)
        return book


schema = strawberry.Schema(query=Query, mutation=Mutation)
q = 'mutation { addBook(data: {title: "Dune", author: "Herbert"}) { id title } }'
print(schema.execute_sync(q).data)

Async Resolvers

Because Strawberry runs on ASGI, resolvers can be async. This matters on FastAPI: your resolvers will await database calls, HTTP clients, or cache lookups without blocking the event loop.

  • Just declare async def — Strawberry awaits it for you.
  • Mix sync and async resolvers freely in the same schema.
  • Use async resolvers for any I/O so a single GraphQL request with many fields stays non-blocking.
import strawberry
import asyncio


@strawberry.type
class Stats:
    total_books: int


async def fetch_count() -> int:
    await asyncio.sleep(0)  # stand-in for an async DB call
    return 42


@strawberry.type
class Query:
    @strawberry.field
    async def stats(self) -> Stats:
        return Stats(total_books=await fetch_count())


schema = strawberry.Schema(query=Query)
print(asyncio.run(schema.execute("{ stats { totalBooks } }")).data)

Mounting on FastAPI with GraphQLRouter

Strawberry ships strawberry.fastapi.GraphQLRouter, an APIRouter you mount with app.include_router. It serves the GraphQL endpoint and an in-browser IDE (GraphiQL) at the same path.

  • Pass your built schema to the router.
  • Mount it under a path like /graphql.
  • The router uses FastAPI's event loop, so async resolvers and FastAPI startup/shutdown events work together.

This is framework code, so it is not runnable on a bare judge.

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


@strawberry.type
class Query:
    @strawberry.field
    def hello(self) -> str:
        return "world"


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

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

Sharing Dependencies via Context

The big payoff of mounting on FastAPI is shared dependencies. Pass a context_getter to GraphQLRouter — it is a FastAPI dependency callable, so it can itself Depends on a DB session, the current user, or a settings object.

Whatever the context getter returns is exposed to resolvers via strawberry.Info at info.context. This is how authentication and database sessions flow from FastAPI into your GraphQL resolvers.

from fastapi import Depends
from strawberry.fastapi import GraphQLRouter


async def get_db():
    # yield a real async session in production
    yield {"connection": "db-session"}


async def get_context(db=Depends(get_db)):
    return {"db": db, "role": "reader"}


graphql_app = GraphQLRouter(schema, context_getter=get_context)

Reading Context Inside Resolvers

To use the shared context, add an info: strawberry.Info parameter to a resolver. Strawberry injects it automatically and it never appears as a GraphQL argument. Access your dependencies through info.context.

  • info.context["db"] — the session provided by context_getter.
  • Use it to authorize: read the current user and raise on missing permissions.
  • The same context object is shared across every resolver in a single request.
import strawberry


@strawberry.type
class Query:
    @strawberry.field
    def current_role(self, info: strawberry.Info) -> str:
        return info.context["role"]

    @strawberry.field
    def secret(self, info: strawberry.Info) -> str:
        if info.context["role"] != "admin":
            raise Exception("forbidden")
        return "top-secret"

Quick Check: Sharing the DB session

You mounted a Strawberry schema on FastAPI and need each GraphQL resolver to use the same per-request database session that your REST endpoints get from a FastAPI dependency. What is the idiomatic Strawberry + FastAPI way to wire this up?

Recap

You built a typed GraphQL schema with Strawberry and mounted it on FastAPI:

  • Types: @strawberry.type classes with type-hinted fields; strawberry.ID for identifiers and Optional for nullable fields.
  • Query: the read root, with @strawberry.field resolvers whose parameters become GraphQL arguments.
  • Mutations: @strawberry.mutation methods that take an @strawberry.input type and return the affected object.
  • Schema: strawberry.Schema(query=Query, mutation=Mutation), verifiable with execute_sync.
  • FastAPI integration: mount with GraphQLRouter, and share DB sessions and auth through context_getter + info.context, reusing FastAPI's dependency injection.

This code-first, type-safe approach keeps your GraphQL API and your FastAPI app speaking the same language — Python type hints.

자주 묻는 질문

“타입, 쿼리 및 변형 정의” 강의는 무료인가요?

네 — “타입, 쿼리 및 변형 정의” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 FastAPI Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. FastAPI Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“타입, 쿼리 및 변형 정의”에서 뭘 배우나요?

Strawberry로 타입이 지정된 GraphQL 스키마를 구축하고 공유 의존성을 사용하는 FastAPI 앱에 연결합니다. 브라우저에서 직접 실행하는 실습 코드로 FastAPI Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

FastAPI Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 FastAPI Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.

“타입, 쿼리 및 변형 정의” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 FastAPI Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 FastAPI Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 타입, 쿼리 및 변형 정의
  2. DataLoaders로 N+1 쿼리 해결
  3. 실시간 GraphQL 구독
  4. 쿼리 비용 분석과 깊이 제한
← FastAPI Backend Development Bootcamp(으)로 돌아가기