Sebagian besar API perlu merepresentasikan relasi antar model one-to-many, many-to-many, atau one-to-one. Serializer di Django REST Framework (DRF) memiliki beberapa cara untuk mengendalikan bagaimana object terkait muncul di API baik berupa string, ID, field khusus, maupun nested data.
Tujuan kita mempelajari cara menangani relasi pada serializer adalah agar kita bisa:
- Memilih cara terbaik untuk menampilkan data terkait: sebagai string (mudah dibaca), sebagai ID (untuk link), sebagai unique field (contoh: email atau slug), atau sebagai nested object (informasi lengkap).
- Membuat API lebih mudah digunakan oleh client berbeda. Beberapa hanya butuh ID, sementara yang lainnya mungkin butuh detail dengan lebih lengkap.
- Mendukung operation read dan write relasi yang sesuai kebutuhan.
- Menghindari performance issue (seperti N+1 query) dengan memahami bagaimana akses relasi memengaruhi query database.
Memahami hal ini akan membantu kita untuk dapat membuat API yang fleksibel, efisien, dan lebih mudah di-maintain.
Jenis Field Relasi di DRF
- Menampilkan nilai
__str__ dari object terkait (default: read-only) - Cocok untuk output yang sederhana dan mudah dibaca
- Menampilkan primary key (ID) dari object terkait
- Cocok untuk operasi write (POST/PUT) dan untuk menghubungkan object
- Menampilkan atribut tertentu (contoh:
username, slug, code) dari object terkait - Berguna jika kita ingin menggunakan field unik selain ID
Nested Serializer
- Melakukan embed object terkait (atau subsetnya) menggunakan serializer lainnya
- Cocok untuk data yang kompleks atau client membutuhkan detail yang lengkap
Latihan relationship_lab
Kita akan membuat app baru agar tidak menganggu app lainnya.
Buat app dan daftarkan
1
2
3
| cd my-drf-project
source .venv/bin/activate
python manage.py startapp relationship_lab
|
Tambahkan 'relationship_lab' ke INSTALLED_APPS di config/settings.py.
Buat model di relationship_lab/models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="articles")
published_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
|
Makemigrations dan migrate
1
2
| python manage.py makemigrations relationship_lab
python manage.py migrate
|
Tambahkan sample data
Buka shell:
Jalankan skrip ini:
1
2
3
4
5
6
7
8
9
| from relationship_lab.models import Author as RLAuthor, Article as RLArticle
RLAuthor.objects.all().delete()
RLArticle.objects.all().delete()
a1 = RLAuthor.objects.create(name="Alice", email="alice@example.com")
a2 = RLAuthor.objects.create(name="Bob", email="bob@example.com")
RLArticle.objects.create(title="DRF Relationships 101 (RL)", slug="rl-drf-relationships-101", content="Intro to DRF relationships", author=a1)
RLArticle.objects.create(title="Nested Serializers (RL)", slug="rl-nested-serializers", content="Deep dive into nesting", author=a2)
|
Serializer untuk Relasi
Buat file relationship_lab/serializers.py:
1
2
3
4
5
6
7
8
| from rest_framework import serializers
from .models import Author, Article
class ArticleStringAuthorSerializer(serializers.ModelSerializer):
author = serializers.StringRelatedField()
class Meta:
model = Article
fields = ["id", "title", "author", "slug"]
|
Tes di shell:
1
2
3
4
5
6
7
| from relationship_lab.models import Article as RLArticle
from relationship_lab.serializers import ArticleStringAuthorSerializer
art = RLArticle.objects.first()
ser = ArticleStringAuthorSerializer(art)
print(ser.data)
# Output: {'id': 1, 'title': 'DRF Relationships 101 (RL)', 'author': 'Alice', 'slug': 'rl-drf-relationships-101'}
|
1
2
3
4
5
| class ArticlePKAuthorSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all())
class Meta:
model = Article
fields = ["id", "title", "author", "slug"]
|
Tes di shell:
1
2
3
4
5
6
7
8
| # PENTING: Sebelum menjalankan kode ini, pastikan shell kita baru (fresh).
# Untuk menghindari 'ImportError', restart shell.
from relationship_lab.models import Article as RLArticle
from relationship_lab.serializers import ArticlePKAuthorSerializer
art = RLArticle.objects.first()
ser = ArticlePKAuthorSerializer(art)
print(ser.data)
# Output: {'id': 1, 'title': 'DRF Relationships 101 (RL)', 'author': 1, 'slug': 'rl-drf-relationships-101'}
|
1
2
3
4
5
| class ArticleSlugAuthorSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(slug_field="email", queryset=Author.objects.all())
class Meta:
model = Article
fields = ["id", "title", "author", "slug"]
|
Tes di shell:
1
2
3
4
5
6
7
8
| # PENTING: Sebelum menjalankan kode ini, pastikan shell kita baru (fresh).
# Untuk menghindari 'ImportError', restart shell.
from relationship_lab.models import Article as RLArticle
from relationship_lab.serializers import ArticleSlugAuthorSerializer
art = RLArticle.objects.first()
ser = ArticleSlugAuthorSerializer(art)
print(ser.data)
# Output: {'id': 1, 'title': 'DRF Relationships 101 (RL)', 'author': 'alice@example.com', 'slug': 'rl-drf-relationships-101'}
|
Contoh Nested Serializer
1
2
3
4
5
6
7
8
9
10
11
12
| # PENTING: Sebelum menjalankan kode ini, pastikan shell kita baru (fresh).
# Untuk menghindari 'ImportError', restart shell.
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ["id", "name", "email"]
class ArticleNestedAuthorSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
class Meta:
model = Article
fields = ["id", "title", "author", "slug"]
|
Tes di shell:
1
2
3
4
5
6
| from relationship_lab.models import Article as RLArticle
from relationship_lab.serializers import ArticleNestedAuthorSerializer
art = RLArticle.objects.first()
ser = ArticleNestedAuthorSerializer(art)
print(ser.data)
# Output: {'id': 1, 'title': 'DRF Relationships 101 (RL)', 'author': {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}, 'slug': 'rl-drf-relationships-101'}
|
Sekilas Mengenai Problem N+1 Query
Disaat kita menggunakan nested serializers atau mengakses object terkait di dalam loop, Django bisa menjalankan query tambahan untuk setiap objek terkait. Ini disebut N+1 query problem.
Contoh:
1
2
3
| from relationship_lab.models import Article as RLArticle
for article in RLArticle.objects.all():
print(article.author.name) # Ini dapat menyebabkan 1 query untuk articles + N query untuk authors
|
Cara menghindari:
Gunakan .select_related("author") untuk mengambil author terkait dalam satu query:
1
2
3
| articles = RLArticle.objects.select_related("author")
for article in articles:
print(article.author.name) # Hanya 1 query untuk articles + 1 untuk semua authors
|
Lebih lengkap tentang cara kerja dan perbandingan select_related vs prefetch_related akan kita bahas di materi lanjutan.
Panduan Mudah Memilih Penggunaan Field Type
Intinya: Apa yang akan dilakukan client dengan data ini?
Gunakan StringRelatedField. Mudah dibaca dan sudah cukup untuk menampilkan informasi sederhana. Catatan: ini read-only, tidak cocok bila client perlu mengirim data kembali.
Apakah client mengirim data untuk membuat atau melakukan update relasi?
Gunakan PrimaryKeyRelatedField. Jika client mengirim {“author”: 1} dan Django tahu objek Author mana yang dimaksud. Ini umum dipakai untuk operation write.
Gunakan SlugRelatedField. Contoh: {“author”: “alice@example.com”}. Ini lebih natural untuk interface tertentu, bisa digunakan selama field yang dipakai unique.
Istilah SlugRelatedField sebenarnya agak membingungkan. Meskipun namanya slug field, kita tidak harus menggunakan SlugField di model. slug_field hanya menunjukkan field unik mana yang dipakai sebagai lookup (bisa email, username, dll).
Apakah client perlu detail lengkap objek terkait dalam satu response?
Gunakan nested serializer. Client mendapat semua data sekaligus, tapi ini berpotensi membuat payload menjadi berat dan menyebabkan risiko N+1 seperti yang sudah disampaikan diatas.
Decision Flow Sederhana:
1
2
3
4
5
6
7
8
| Butuh support untuk write?
├── Tidak → StringRelatedField (hanya untuk display)
└── Ya → Apakah client tahu ID?
├── Ya → PrimaryKeyRelatedField
└── Tidak → SlugRelatedField (pakai field unik yang diketahui)
Butuh detail lengkap dalam satu response?
└── Ya → Nested Serializer (tapi perhatikan performa/query)
|
Foto cover oleh miinyuii dari Unsplash