Strawberry is now the recommended way to build GraphQL in Django — not Graphene. It uses Python type annotations natively, which means your GraphQL schema and your Python types stay in sync automatically, and mypy can verify them. Graphene uses a class-based approach that predates Python's typing system and requires more boilerplate.
This guide builds a complete GraphQL API from scratch with Django + Strawberry.
Installation
pip install strawberry-graphql[django]
Add to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"strawberry.django",
]
Defining Types
Strawberry types are Python dataclasses decorated with @strawberry.type:
# schema.py
import strawberry
from typing import Optional
from datetime import datetime
@strawberry.type
class AuthorType:
id: int
name: str
email: str
@strawberry.type
class BlogPostType:
id: int
title: str
slug: str
excerpt: str
published_at: Optional[datetime]
author: AuthorType
With strawberry.django, you can derive types directly from Django models:
import strawberry_django
from .models import Blog, User
@strawberry_django.type(User)
class AuthorType:
id: strawberry.auto
name: strawberry.auto
email: strawberry.auto
@strawberry_django.type(Blog)
class BlogPostType:
id: strawberry.auto
title: strawberry.auto
slug: strawberry.auto
excerpt: strawberry.auto
published_at: strawberry.auto
author: AuthorType
strawberry.auto tells Strawberry to infer the type from the Django field definition.
Writing Queries
@strawberry.type
class Query:
@strawberry.field
def blog_posts(self, info) -> list[BlogPostType]:
return Blog.objects.filter(status="published").select_related("author")
@strawberry.field
def blog_post(self, info, slug: str) -> Optional[BlogPostType]:
try:
return Blog.objects.select_related("author").get(slug=slug, status="published")
except Blog.DoesNotExist:
return None
@strawberry.field
def my_posts(self, info) -> list[BlogPostType]:
user = info.context.request.user
if not user.is_authenticated:
raise Exception("Authentication required")
return Blog.objects.filter(author=user)
Note the N+1 avoidance: select_related("author") in the queryset. Without this, fetching 10 blog posts triggers 11 queries (one per author access).
Mutations
Mutations modify data. Define input types and mutation resolvers:
@strawberry.input
class CreateBlogInput:
title: str
content: str
status: str = "draft"
@strawberry.type
class CreateBlogResult:
post: Optional[BlogPostType] = None
error: Optional[str] = None
@strawberry.type
class Mutation:
@strawberry.mutation
def create_blog_post(self, info, input: CreateBlogInput) -> CreateBlogResult:
user = info.context.request.user
if not user.is_authenticated:
return CreateBlogResult(error="Authentication required")
post = Blog.objects.create(
title=input.title,
content=input.content,
status=input.status,
author=user,
)
return CreateBlogResult(post=post)
The result type pattern (returning either data or an error) is better than raising exceptions — it makes error handling explicit in the GraphQL schema.
Schema Assembly and URL Setup
# schema.py
schema = strawberry.Schema(query=Query, mutation=Mutation)
# urls.py
from strawberry.django.views import GraphQLView
from .schema import schema
urlpatterns = [
path("graphql/", GraphQLView.as_view(schema=schema)),
]
Visit /graphql/ in development for the GraphiQL interactive playground — Strawberry includes it automatically.
Authentication
For JWT authentication, pass the token in the Authorization header and validate it in your resolvers:
# permissions.py
import strawberry
from strawberry.permission import BasePermission
from strawberry.types import Info
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
def has_permission(self, source, info: Info, **kwargs) -> bool:
return info.context.request.user.is_authenticated
# Use in query/mutation
@strawberry.type
class Query:
@strawberry.field(permission_classes=[IsAuthenticated])
def my_profile(self, info) -> AuthorType:
return info.context.request.user
Or use DRF JWT middleware to authenticate before the GraphQL view is called:
# views.py
from strawberry.django.views import GraphQLView
from rest_framework_simplejwt.authentication import JWTAuthentication
class AuthenticatedGraphQLView(GraphQLView):
def get_context(self, request, response):
jwt_auth = JWTAuthentication()
try:
user, _ = jwt_auth.authenticate(request)
request.user = user
except Exception:
pass # Anonymous user
return {"request": request, "response": response}
Subscriptions (Real-Time)
Strawberry supports GraphQL subscriptions over WebSockets via Django Channels:
import asyncio
import strawberry
from typing import AsyncGenerator
@strawberry.type
class Subscription:
@strawberry.subscription
async def count(self, target: int = 10) -> AsyncGenerator[int, None]:
for i in range(target):
yield i
await asyncio.sleep(1)
# asgi.py
from strawberry.channels import GraphqlWsConsumer
from strawberry.django.views import GraphQLView
websocket_urlpatterns = [
re_path("ws/graphql/", GraphqlWsConsumer.as_asgi(schema=schema)),
]
Strawberry vs Graphene: When to Switch
If you have an existing Graphene codebase, migrating is non-trivial. Do not migrate for its own sake. Start with Strawberry for: - New Django projects - New services within an existing project - When type checking coverage matters
Stick with Graphene for: - Existing Graphene codebases that are working - Teams with deep Graphene knowledge
The practical advantage of Strawberry: any Python type checker can verify your resolvers. With Graphene, graphene.String() is not a Python type — mypy cannot help you.
If you need a GraphQL API built in Django with full type safety — using Strawberry for new projects or DRF for REST APIs — hire me as a Django developer and let's discuss the right approach for your requirements.