Featured image of post Menangani Relasi di Serializer DRF

Menangani Relasi di Serializer DRF

Penjelasan tentang cara DRF menangani relasi antar-model (StringRelatedField, PrimaryKeyRelatedField, SlugRelatedField, Nested Serializer), latihan praktikal, dan sekilas mengenai problem N+1 query.

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

StringRelatedField

  • Menampilkan nilai __str__ dari object terkait (default: read-only)
  • Cocok untuk output yang sederhana dan mudah dibaca

PrimaryKeyRelatedField

  • Menampilkan primary key (ID) dari object terkait
  • Cocok untuk operasi write (POST/PUT) dan untuk menghubungkan object

SlugRelatedField

  • 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:

1
python manage.py 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:

Contoh StringRelatedField

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'}

Contoh PrimaryKeyRelatedField

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'}

Contoh SlugRelatedField

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?

Apakah client hanya menampilkan informasi?

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.

Apakah client adalah manusia yang mengetik di form dan ID sulit digunakan?

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

Dibawah Lisensi CC BY-NC-SA 4.0
Dibangun dengan Hugo
Tema Stack dirancang oleh Jimmy