Rohan Yeole - Homepage Rohan Yeole

Django GraphQL with Strawberry: Build a Type-Safe API from Scratch

Apr 24, 20261 min read

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.

Chat with me on WhatsApp