← Back to list

strawberry-graphql
by yonatangross
The Complete AI Development Toolkit for Claude Code — 159 skills, 34 agents, 20 commands, 144 hooks. Production-ready patterns for FastAPI, React 19, LangGraph, security, and testing.
⭐ 29🍴 4📅 Jan 23, 2026
SKILL.md
name: strawberry-graphql description: Strawberry GraphQL library for Python with FastAPI integration, type-safe resolvers, DataLoader patterns, and subscriptions. Use when building GraphQL APIs with Python, implementing real-time features, or creating federated schemas. context: fork agent: backend-system-architect version: 1.0.0 tags: [graphql, strawberry, fastapi, dataloader, subscriptions, federation, python, 2026] author: OrchestKit user-invocable: false
Strawberry GraphQL Patterns
Type-safe GraphQL in Python with code-first schema definition.
Overview
- Complex data relationships (nested queries, multiple entities)
- Client-driven data fetching (mobile apps, SPAs)
- Real-time features (subscriptions for live updates)
- Federated microservice architecture
When NOT to Use
- Simple CRUD APIs (REST is simpler)
- Internal microservice communication (use gRPC)
Schema Definition
import strawberry
from datetime import datetime
from strawberry import Private
@strawberry.enum
class UserStatus:
ACTIVE = "active"
INACTIVE = "inactive"
@strawberry.type
class User:
id: strawberry.ID
email: str
name: str
status: UserStatus
password_hash: Private[str] # Not exposed in schema
@strawberry.field
def display_name(self) -> str:
return f"{self.name} ({self.email})"
@strawberry.field
async def posts(self, info: strawberry.Info, limit: int = 10) -> list["Post"]:
return await info.context.post_loader.load_by_user(self.id, limit)
@strawberry.type
class Post:
id: strawberry.ID
title: str
content: str
author_id: strawberry.ID
@strawberry.field
async def author(self, info: strawberry.Info) -> User:
return await info.context.user_loader.load(self.author_id)
@strawberry.input
class CreateUserInput:
email: str
name: str
password: str
Query and Mutation
@strawberry.type
class Query:
@strawberry.field
async def user(self, info: strawberry.Info, id: strawberry.ID) -> User | None:
return await info.context.user_service.get(id)
@strawberry.field
async def me(self, info: strawberry.Info) -> User | None:
user_id = info.context.current_user_id
return await info.context.user_service.get(user_id) if user_id else None
@strawberry.type
class Mutation:
@strawberry.mutation
async def create_user(self, info: strawberry.Info, input: CreateUserInput) -> User:
return await info.context.user_service.create(
email=input.email, name=input.name, password=input.password
)
@strawberry.mutation
async def delete_user(self, info: strawberry.Info, id: strawberry.ID) -> bool:
await info.context.user_service.delete(id)
return True
DataLoader (N+1 Prevention)
from strawberry.dataloader import DataLoader
class UserLoader(DataLoader[str, User]):
def __init__(self, user_repo):
super().__init__(load_fn=self.batch_load)
self.user_repo = user_repo
async def batch_load(self, keys: list[str]) -> list[User]:
users = await self.user_repo.get_many(keys)
user_map = {u.id: u for u in users}
return [user_map.get(key) for key in keys]
class GraphQLContext:
def __init__(self, request, user_service, user_repo, post_repo):
self.request = request
self.user_service = user_service
self.user_loader = UserLoader(user_repo)
self._current_user_id = None
@property
def current_user_id(self) -> str | None:
if self._current_user_id is None:
token = self.request.headers.get("authorization", "").replace("Bearer ", "")
self._current_user_id = decode_token(token) if token else None
return self._current_user_id
FastAPI Integration
from fastapi import FastAPI, Request, Depends
from strawberry.fastapi import GraphQLRouter
schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
async def get_context(request: Request, user_service=Depends(get_user_service)) -> GraphQLContext:
return GraphQLContext(request=request, user_service=user_service, ...)
graphql_router = GraphQLRouter(schema, context_getter=get_context, graphiql=True)
app = FastAPI()
app.include_router(graphql_router, prefix="/graphql")
Subscriptions
from typing import AsyncGenerator
@strawberry.type
class Subscription:
@strawberry.subscription
async def user_updated(self, info: strawberry.Info, user_id: strawberry.ID) -> AsyncGenerator[User, None]:
async for message in info.context.pubsub.subscribe(f"user:{user_id}:updated"):
yield User(**message)
@strawberry.subscription
async def notifications(self, info: strawberry.Info) -> AsyncGenerator["Notification", None]:
user_id = info.context.current_user_id
if not user_id:
raise PermissionError("Authentication required")
async for message in info.context.pubsub.subscribe(f"user:{user_id}:notifications"):
yield Notification(**message)
Authentication and Authorization
from strawberry.permission import BasePermission
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
async def has_permission(self, source, info: strawberry.Info, **kwargs) -> bool:
return info.context.current_user_id is not None
class IsAdmin(BasePermission):
message = "Admin access required"
async def has_permission(self, source, info: strawberry.Info, **kwargs) -> bool:
user_id = info.context.current_user_id
if not user_id:
return False
user = await info.context.user_service.get(user_id)
return user and user.role == "admin"
# Usage
@strawberry.type
class Query:
@strawberry.field(permission_classes=[IsAuthenticated])
async def me(self, info: strawberry.Info) -> User:
return await info.context.user_service.get(info.context.current_user_id)
@strawberry.field(permission_classes=[IsAdmin])
async def all_users(self, info: strawberry.Info) -> list[User]:
return await info.context.user_service.list_all()
Error Handling with Union Types
@strawberry.type
class CreateUserSuccess:
user: User
@strawberry.type
class UserError:
message: str
code: str
field: str | None = None
@strawberry.type
class CreateUserError:
errors: list[UserError]
CreateUserResult = strawberry.union("CreateUserResult", [CreateUserSuccess, CreateUserError])
@strawberry.type
class Mutation:
@strawberry.mutation
async def create_user(self, info: strawberry.Info, input: CreateUserInput) -> CreateUserResult:
errors = []
if not is_valid_email(input.email):
errors.append(UserError(message="Invalid email", code="INVALID_EMAIL", field="email"))
if errors:
return CreateUserError(errors=errors)
try:
user = await info.context.user_service.create(**input.__dict__)
return CreateUserSuccess(user=user)
except DuplicateEmailError:
return CreateUserError(errors=[UserError(message="Email exists", code="DUPLICATE_EMAIL", field="email")])
Key Decisions
| Decision | Recommendation |
|---|---|
| Schema approach | Code-first with Strawberry types |
| N+1 prevention | DataLoader for all nested resolvers |
| Pagination | Relay-style cursor pagination |
| Auth | Permission classes, context-based |
| Errors | Union types for mutations |
| Subscriptions | Redis PubSub for horizontal scaling |
Anti-Patterns (FORBIDDEN)
# NEVER make database calls in resolver loops (N+1 queries!)
for post_id in self.post_ids:
posts.append(await db.get_post(post_id))
# CORRECT: Use DataLoader
return await info.context.post_loader.load_many(self.post_ids)
# NEVER expose internal IDs without encoding
id: int # Exposes auto-increment ID!
# CORRECT: Use opaque IDs
id: strawberry.ID # base64 encoded
# NEVER skip input validation in mutations
Related Skills
api-design-framework- REST API patternsgrpc-python- gRPC alternativestreaming-api-patterns- WebSocket patterns
Score
Total Score
75/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
✓説明文
100文字以上の説明がある
+10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
