Dari REST ke GraphQL dengan Strawberry + Django

Panduan praktis migrasi REST API Django ke GraphQL pakai Strawberry — setup, schema, resolver, DataLoader, autentikasi, file upload, plus perbandingan dengan Graphene.

Frustrasi dengan REST yang Makin Membengkak

Kamu pakai Django REST Framework dan mulai frustrasi dengan over-fetching, under-fetching, dan jumlah endpoint yang membengkak tiap kali frontend minta field baru? Kamu sudah dengar GraphQL bisa jadi solusi, tapi ngelihat Graphene yang terakhir rilis stabil di 2022 dan terasa heavy bikin ragu.

Strawberry menjawab itu: library GraphQL Python yang modern, memanfaatkan type hints, async dari awal, dan punya integrasi Django first-class. Ditulis di atas Rust core (graphql-core-next); lebih cepat, lebih hemat memori. Ini GraphQL untuk developer Django yang pengen developer experience maksimal tanpa sakit kepala.

Kalau kamu belum familiar dengan perbandingan REST vs GraphQL, cek juga artikel kami tentang FastAPI vs Django REST untuk konteks lebih lanjut.

Masalah Klasik REST Django: Over-fetching sampai Versioning

REST API Django kamu selama ini berjalan lancar. Tapi seiring fitur bertambah, masalah klasik mulai muncul:

  • Over-fetching: Endpoint /api/fruits/ ngirim 20 field, padahal frontend cuma butuh name dan price.
  • Under-fetching: Halaman detail perlu 3-4 endpoint terpisah: fruits, categories, reviews.
  • Versioning: Setiap perubahan response butuh endpoint baru atau field deprecation yang messy.
  • Dokumentasi: Harus maintain DRF Spectacular atau drf-yasg terpisah.

GraphQL menyelesaikan ini: satu endpoint, client menentukan field yang diminta, tipe terdefinisi di schema, dan dokumentasi otomatis via GraphiQL.

Tapi kenapa Strawberry, bukan Graphene?

Aspek Graphene Strawberry
Type system Class-based (terpisah dari model) Type hints Python native
Async support Plugin tambahan Built-in, first-class
DataLoader Library terpisah (dataloader) Built-in (strawberry.dataloader)
Django integration graphene-django (maintenance lambat) strawberry-django (aktif, FastAPI-style)
Core engine graphql-core (pure Python) graphql-core-next (Rust binding)
Release terakhir 2022 2025 (aktif)

Strawberry lebih modern, lebih cepat, dan lebih mudah dipelajari kalau kamu sudah familiar dengan Python type hints.

Setup Strawberry di Django

Kita akan bikin GraphQL endpoint untuk model Fruit dan Category dari aplikasi Django sederhana.

1. Setup & Model

# models.py
from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)

    def __str__(self):
        return self.name

class Fruit(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    category = models.ForeignKey(
        Category, on_delete=models.CASCADE, related_name="fruits"
    )
    is_available = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
pip install 'strawberry-graphql[django]'

2. Tipe GraphQL via Strawberry-Django

Ini keunggulan utama Strawberry: kamu bisa auto-map model ke tipe GraphQL tanpa boilerplate.

# schema.py
import strawberry
import strawberry_django
from strawberry_django.optimizer import DjangoOptimizerExtension

from . import models

@strawberry_django.type(models.Category, fields="__all__")
class CategoryType:
    pass

@strawberry_django.type(models.Fruit, fields="__all__")
class FruitType:
    pass

Dengan fields="__all__", Strawberry otomatis generate field berdasarkan model. Kamu juga bisa selektif:

@strawberry_django.type(models.Fruit, only=["name", "price"])
class FruitSummary:
    pass

3. Query + Resolver

@strawberry.type
class Query:
    fruits: list[FruitType] = strawberry.django.field()
    fruit: FruitType | None = strawberry.django.field()

    @strawberry.field
    def fruits_by_category(self, category_slug: str) -> list[FruitType]:
        return models.Fruit.objects.filter(
            category__slug=category_slug,
            is_available=True,
        )

Strawberry auto-generates resolver untuk field deklaratif (fruits, fruit). Untuk logika kustom, kamu tinggal pakai decorator @strawberry.field.

4. Schema + DjangoOptimizerExtension

schema = strawberry.Schema(
    query=Query,
    extensions=[
        DjangoOptimizerExtension(),
    ],
)

DjangoOptimizerExtension menyelesaikan masalah N+1 secara otomatis. Tanpa optimizer, query GraphQL yang minta fruit.category.name bakal N+1 queries. Dengan extension ini, Strawberry otomatis melakukan select_related() / prefetch_related() berdasarkan query yang masuk, tanpa perlu @staticmethod resolver manual.

5. Routing: AsyncGraphQLView

# urls.py
from django.urls import path
from strawberry.django.views import AsyncGraphQLView

from .schema import schema

urlpatterns = [
    path(
        "graphql/",
        AsyncGraphQLView.as_view(schema=schema),
    ),
]

Gunakan AsyncGraphQLView meskipun kamu belum migrasi penuh ke async. Strawberry handle sync-to-async di balik layar. Kalau prefer sync (misal masih pakai WSGI), ganti ke GraphQLView.

# Kalau masih WSGI / sync:
from strawberry.django.views import GraphQLView

6. DataLoader: Hindari N+1

Meskipun DjangoOptimizerExtension sudah handle banyak kasus, kadang kamu butuh kustomisasi query. DataLoader untuk batch & cache per request:

# dataloaders.py
from strawberry.dataloader import DataLoader
from django.db.models import Model

from . import models

async def load_fruits_by_category(keys: list[int]) -> list[list[Model]]:
    fruits = models.Fruit.objects.filter(category_id__in=keys, is_available=True)
    result = {k: [] for k in keys}
    for fruit in fruits:
        result[fruit.category_id].append(fruit)
    return [result[key] for key in keys]

category_fruits_loader = DataLoader(load_fn=load_fruits_by_category)

Integrasi dengan context per-request:

class CustomContext:
    def __init__(self, request):
        self.request = request
        self.category_fruits_loader = DataLoader(
            load_fn=load_fruits_by_category
        )

@strawberry.type
class Query:
    @strawberry.field
    async def category_with_fruits(
        self, info: strawberry.types.Info
    ) -> list[CategoryType]:
        loader = info.context.category_fruits_loader
        # DataLoader cache per request — aman
        return await loader.load(1)

schema = strawberry.Schema(
    query=Query,
    extensions=[DjangoOptimizerExtension()],
)

Pitfall: DataLoader cache-nya per instance. Kalau kamu membuat instance baru di tiap resolver, cache-nya tidak terpakai. Pastikan DataLoader dibuat sekali di context dan di-reuse.

7. Autentikasi via Custom Context

# context.py
from django.contrib.auth import get_user_model
from django.utils.functional import SimpleLazyObject

def get_custom_context(request):
    return {
        "request": request,
        "user": request.user if request.user.is_authenticated else None,
    }

# urls.py
from strawberry.django.views import AsyncGraphQLView

urlpatterns = [
    path(
        "graphql/",
        AsyncGraphQLView.as_view(
            schema=schema,
            context_getter=get_custom_context,
        ),
    ),
]

Di resolver:

@strawberry.type
class Query:
    @strawberry.field
    def me(self, info: strawberry.types.Info) -> UserType | None:
        user = info.context.user
        if not user:
            raise PermissionError("Not authenticated")
        return user

8. File Upload

File upload di GraphQL agak tricky. Strawberry dukung via Upload scalar.

from strawberry.file_uploads import Upload

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def upload_fruit_image(
        self, fruit_id: int, file: Upload
    ) -> FruitType:
        fruit = await models.Fruit.objects.aget(id=fruit_id)
        fruit.image.save(file.name, file)
        await fruit.asave()
        return fruit

Enable di view:

from strawberry.django.views import AsyncGraphQLView

urlpatterns = [
    path(
        "graphql/",
        AsyncGraphQLView.as_view(
            schema=schema,
            multipart_uploads_enabled=True,
        ),
    ),
]

Pitfall: multipart_uploads_enabled default-nya False. Jangan lupa di-set, atau upload akan selalu gagal dengan error “Unable to process upload”.

Kapan Pakai Strawberry, Kapan Tidak

Situasi yang Cocok untuk Strawberry + GraphQL

Situasi Cocok?
Banyak client (mobile, web, third-party) dengan kebutuhan field berbeda ✅ Sangat cocok
Dashboard / admin panel internal ✅ Cocok
API publik untuk konsumen eksternal ✅ Cocok (dokumentasi otomatis)
REST API sederhana dengan 5-10 endpoint ❌ Overkill
Tim baru belajar Django ❌ Tambah complexity
Butuh caching HTTP (GET request) GraphQL default POST, perlu setup tambahan

Kapan Strawberry Kalah dari Graphene?

  • Ekosistem plugin: Graphene lebih mature dengan graphene-django-cud, graphene-subscriptions, dll.
  • Legacy codebase: Kalau project udah pakai Graphene, migrasi ke Strawberry butuh effort; evaluate dulu.
  • Subscription: Strawberry support, tapi graphene-subscriptions lebih stabil.
  • Community size: Graphene masih lebih besar di Stack Overflow / tutorial lama.

Strawberry Pain Points

  1. SynchronousOnlyOperation: Kalau kamu mengakses ORM dari thread async tanpa sync_to_async, Django lempar error. Solusi: pastikan AsyncGraphQLView dan pakai aget/asave.
  2. DataLoader cache antar-request: Jangan simpan DataLoader di module-level. Selalu buat per-request via context.
  3. File upload tidak aktif secara default: Sudah disebut di atas; jangan lupa multipart_uploads_enabled=True.
  4. DjangoOptimizerExtension bukan solusi universal: Untuk query yang melibatkan annotate, aggregate, atau custom SQL, optimizer tidak cukup; kamu tetap perlu DataLoader.

Strawberry dan GraphQL: Mulai dari Schema

Strawberry membawa GraphQL ke Django dengan cara yang modern dan Pythonic. Type hints, async built-in, DataLoader, dan DjangoOptimizerExtension bikin developer experience jauh lebih baik dibanding Graphene.

Kalau kamu mulai project Django baru hari ini dan API kamu punya relasi model kompleks atau multiple clients; coba Strawberry. Setup cuma 15 menit, dan kamu langsung dapet:

  • Satu endpoint dengan dokumentasi interaktif
  • Query fleksibel tanpa versioning
  • Performa N+1 yang terhandle otomatis

Next: Install strawberry-graphql[django], copy schema di atas, dan coba query pertamamu di GraphiQL (/graphql/).

Punya pengalaman migrasi REST ke GraphQL? Atau nemu use case unik? Mention di Twitter/X (@strawberrypy dan @djangoproject).