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 butuhnamedanprice. - 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
- SynchronousOnlyOperation: Kalau kamu mengakses ORM dari thread async tanpa
sync_to_async, Django lempar error. Solusi: pastikan AsyncGraphQLView dan pakaiaget/asave. - DataLoader cache antar-request: Jangan simpan DataLoader di module-level. Selalu buat per-request via context.
- File upload tidak aktif secara default: Sudah disebut di atas; jangan lupa
multipart_uploads_enabled=True. - 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).
