Belajar Django - Django REST Framework (DRF)
Episode 11 of 27

Belajar Django - Django REST Framework (DRF)

Membangun REST API dengan Django REST Framework (DRF): Serializers, ViewSets, dan Routers, autentikasi & permission, pagination, serta praktik lengkap REST API CRUD untuk post blog dengan autentikasi JWT memakai SimpleJWT.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Sampai episode 10, semua output adalah HTML untuk browser. Tapi dunia 2026 butuh lebih dari itu: aplikasi mobile, SPA, dan integrasi pihak ketiga berbicara lewat API. Di episode ini kita membangun REST API dengan Django REST Framework (DRF) — framework de facto untuk API di ekosistem Django.

Mengapa topik ini penting? Karena API adalah antarmuka publik kedua dari aplikasi kalian (selain halaman web), dan antarmuka ini paling sering di-scrape, di-spam, dan diserang. DRF mengotomatiskan serializer, router, dan dokumentasi, tetapi autentikasi dan permission tetap keputusan kalian — dan di situlah keamanan API ditentukan.

Instalasi dan Konfigurasi

Install DRF dan SimpleJWT
pip install djangorestframework djangorestframework-simplejwt
Pythonsettings.py - daftarkan DRF
INSTALLED_APPS = [
    ...
    "rest_framework",
    "rest_framework_simplejwt",
]
 
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ),
    "DEFAULT_PERMISSION_CLASSES": (
        "rest_framework.permissions.IsAuthenticated",
    ),
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 10,
}

DEFAULT_PERMISSION_CLASSES default IsAuthenticated memaksa semua endpoint login dulu — kita longgarkan per-endpoint untuk data publik (post list). Pagination diaktifkan global dengan PAGE_SIZE.

Serializer

Serializer adalah jembatan model ↔ JSON: ia memutuskan field mana yang diekspos dan bagaimana validasinya.

Pythonblog/serializers.py
from rest_framework import serializers
 
from .models import Comment, Post
 
class CommentSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField(read_only=True)
 
    class Meta:
        model = Comment
        fields = ["id", "author", "body", "created_at"]
        read_only_fields = ["author", "post"]
 
class PostSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField(read_only=True)
    category = serializers.SlugRelatedField(slug_field="slug", read_only=True)
    comment_count = serializers.IntegerField(read_only=True)
 
    class Meta:
        model = Post
        fields = ["id", "title", "slug", "body", "status", "category",
                  "author", "comment_count", "created_at"]

ModelSerializer menurunkan field dari model. StringRelatedField menampilkan __str__ relasi. comment_count adalah field virtual yang akan kita isi dari annotate — bukan field model. Serializer inilah yang memisahkan "data publik" dari "data internal" (misal status tidak boleh di-set client secara bebas).

ViewSet dan Router

ViewSet menggabungkan action list/detail/create/update/delete dalam satu class. Router lalu men-generate URL-nya secara otomatis:

Pythonblog/views.py - ViewSet
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAdminUser
 
from .models import Post
from .serializers import PostSerializer
 
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.filter(status="published")
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    lookup_field = "slug"
 
    def get_queryset(self):
        qs = super().get_queryset()
        if self.request.user.is_staff:
            qs = Post.objects.all()
        return qs.annotate(comment_count=Count("comments")).select_related("author", "category")
 
    @action(detail=False, methods=["post"], permission_classes=[IsAdminUser])
    def publish_all(self, request):
        updated = Post.objects.filter(status="draft").update(status="published")
        return Response({"updated": updated})

IsAuthenticatedOrReadOnly mengizinkan GET publik, menuntut auth untuk write. get_queryset di-override agar staff bisa melihat draft dan setiap post membawa comment_count (anti-N+1 dari episode 6). Custom action publish_all menambahkan endpoint /api/posts/publish_all/ yang hanya bisa dipanggil admin.

URL:

Pythondevblog/urls.py - router
from django.urls import include, path
from rest_framework.routers import DefaultRouter
 
from blog import views
 
router = DefaultRouter()
router.register("posts", views.PostViewSet)
 
urlpatterns = [
    path("api/", include(router.urls)),
    path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]

DefaultRouter men-generate route list (GET/POST /api/posts/), detail (GET/PUT/PATCH/DELETE /api/posts/<slug>/), dan halaman dokumentasi browsable API.

Autentikasi JWT dengan SimpleJWT

JWT (JSON Web Token) membawa klaim (user id, expiry) yang ditandatangani, sehingga server tidak perlu menyimpan state session. SimpleJWT menyediakan endpoint token:

Ambil dan pakai JWT
curl -X POST http://127.0.0.1:8000/api/token/ \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "rahasia"}'

Respons berupa access dan refresh token:

Respons token
{
  "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Lalu kirim access token di header Authorization: Bearer <token>:

Akses endpoint dengan JWT
curl http://127.0.0.1:8000/api/posts/ \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Konfigurasi dasar SimpleJWT di settings:

Pythonsettings.py - SimpleJWT
from datetime import timedelta
 
SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "AUTH_HEADER_TYPES": ("Bearer",),
    "ALGORITHM": "HS256",
}

Warning

Jangan menaruh data sensitif di payload JWT — payload hanya di-encode Base64, bukan dienkripsi; siapa pun bisa membacanya. ACCESS_TOKEN_LIFETIME pendek (15 menit) meminimalkan dampak token bocor; refresh dipakai untuk mendapat access baru. Seluk-beluk refresh, revoke, dan rotasi kita bahas di episode 20.

Pagination dan Filter

Pagination sudah aktif global: respons berbentuk:

Respons pagination
{
  "count": 120,
  "next": "http://127.0.0.1:8000/api/posts/?page=2",
  "previous": null,
  "results": [ ... ]
}

Untuk filtering, pasang django-filter agar field bisa difilter via query param:

Install django-filter
pip install django-filter
Pythonsettings.py + views.py
REST_FRAMEWORK = {
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
    ],
}
Pythonblog/views.py - filter
from django_filters.rest_framework import DjangoFilterBackend
 
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.filter(status="published")
    serializer_class = PostSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ["category__slug", "author__username"]

Sekarang GET /api/posts/?category__slug=django&author__username=admin bekerja langsung. Kombinasi filter + pagination adalah standar API production yang harus kalian biasakan.

Penutup

Inti yang harus dibawa pulang:

  • DRF = ModelSerializer (transformasi data) + ViewSet (logika CRUD) + Router (URL otomatis).
  • permission_classes mengatur akses: IsAuthenticatedOrReadOnly untuk API publik+private.
  • SimpleJWT: access (pendek, 15 menit) + refresh (panjang) dikirim via header Authorization: Bearer.
  • Pagination global via PageNumberPagination; filtering via django-filter.
  • Annotate comment_count di get_queryset untuk performa (N+1 episode 6).

Di episode 12 selanjutnya kita menangani beban baca: Cache & Sessions — framework cache Django (LocMem, Redis, Memcached), caching per-view dan per-template, serta memindahkan session ke Redis agar login cepat dan scalable. Sampai jumpa di episode 12!

Belajar Django - Django REST Framework (DRF) | Belajar Django