Featured image of post Function-Based Views di Django REST Framework

Function-Based Views di Django REST Framework

Penjelasan mengenai Function-Based Views di DRF menggunakan decorator @api_view, HTTP method, object Request dan Response, serta status code.

Kita sudah belajar bagaimana mendesain model dan bagaimana melakukan serialize data ke dan dari JSON pada materi sebelum-sebelumnya. Kedua layer tersebut adalah dasar daru API. Namun, keduanya belum menjawab pertanyaan yang penting:

Bagaimana caranya client dapat mengakses API kita?

Client, seperti mobile app, browser, Postman, Insomnia, atau service lain, mengirimkan HTTP request ke sebuah URL. Ada sesuatu di server kita yang perlu menerima request tersebut, memutuskan apa yang perlu dilakukan, lalu mengirimkan response-nya kembali. “Sesuatu” itu adalah yang akan kita sebut sebagai view.

View adalah layer yang berada di antara HTTP request yang masuk dan semua hal lainnya (model kita, serializer kita, business logic kita). View adalah entry point dan exit point untuk setiap API call.

Catatan: Kita sebelumnya sudah beberapa kali menyebut business logic tanpa benar-benar mendefinisikannya. Business logic artinya aturan dan keputusan yang ada pada aplikasi kita. Contoh: “Hanya admin yang boleh menghapus item”, “Jika order sudah dibayar, tandai sebagai confirmed”, atau “User yang sudah terdaftar harus verifikasi nomor telepon sebelum dapat melakukan transaksi.”

Berikut tiga cara menulis view di DRF, diurutkan mulai dari low-level hingga high-level:

PendekatanDefinisinyaKapan Digunakan
Function-Based Views (FBV)Function Python biasa yang di-decorate dengan @api_viewEndpoint sederhana, belajar, custom logic
Class-Based Views (CBV)Class yang inherit dari APIViewStruktur yang reusable, endpoint yang lebih kompleks
Generic Views / ViewSetsView class bawaan DRF yang sudah siap pakaiCRUD API standar, efisien, cepat, dan mudah diimplementasikan

Catatan: High-level berarti kode kita lebih abstrak: lebih sedikit detail yang harus kita tulis sendiri karena DRF sudah mengurus sebagian besar detailnya.

Materi ini membahas Function-Based Views. Jika kita sudah memahami bagaimana function-based view bekerja, tipe view lainnya akan lebih mudah dipahami karena semuanya menyelesaikan masalah yang sama dengan cara yang berbeda.


Apa Itu HTTP Request?

Sebelum membahas mengenai kode apapun, kita perlu memiliki gambaran yang jelas mengenai apa yang dikirim oleh client dan apa yang kita kirimkan balik.

Saat client memanggil API kita, mereka mengirimkan sebuah HTTP request. Request tersebut punya empat bagian utama:

1
2
3
4
Method:  apa yang ingin dilakukan client (GET, POST, ...)
Path:    /path/ke/resource
Headers: (metadata tentang request-nya)
Body:    (data yang dikirim, jika ada)

Method memberi tahu server apa yang ingin dilakukan client:

HTTP MethodYang Diinginkan ClientPenggunaan Umum di API
GET“Berikan saya data”Membaca sebuah list atau satu item
POST“Buat sesuatu yang baru dengan data ini”Membuat record baru
PUT“Ganti seluruh record ini dengan data baru”Full update
PATCH“Update hanya field-field tertentu ini”Partial update
DELETE“Hapus record ini”Menghapus record

Setelah menerima request, server akan mengirimkan balik sesuatu yang disebut HTTP response. Response tersebut juga punya beberapa bagian:

1
2
3
Status code: 200
Headers: (metadata tentang response-nya)
Body:    (data JSON-nya, kalau ada)

Status code memberi tahu ke client status dari request yang mereka kirimkan:

RangeArtinyaContoh Umum
2xxSukses200 OK, 201 Created, 204 No Content
4xxKesalahan pada Client400 Bad Request, 404 Not Found, 403 Forbidden
5xxKesalahan pada Server500 Internal Server Error

Siklus request/response ini adalah inti dari setiap API. View kita menerima request dan menghasilkan response-nya.


Kenapa View Django Biasa Tidak Cukup

Django sebenarnya sudah punya view sendiri. Lalu kenapa kita perlu menggunakan view milik DRF?

View di Django biasa terlihat seperti ini:

1
2
3
4
from django.http import HttpResponse

def my_view(request):
    return HttpResponse("hello")

View di atas tidak masalah untuk halaman HTML. Namun untuk API JSON, kita butuh lebih banyak lagi:

  • Kita perlu melakukan parsing JSON yang masuk dari request.body secara manual
  • Kita perlu men-set header Content-Type: application/json secara manual
  • Kita perlu menangani HTTP method yang berbeda-beda (GET vs POST vs PUT) dengan statement if
  • Kita perlu me-return status code yang tepat
  • Kita tidak punya validasi otomatis atau integrasi dengan serializer

DRF menjawab semua kebutuhan tersebut. Jika kita menggunakan decorator @api_view dan class Response milik DRF, kita akan mendapatkan:

  • request.data: JSON yang masuk sudah otomatis di-parse menjadi Python dict untuk kita
  • request.query_params: query parameter URL seperti ?status=open sudah otomatis di-parse (dipecah dan diubah menjadi data yang bisa langsung dipakai)
  • request.user: user yang sudah terautentikasi akan otomatis tersedia
  • Response(data): DRF menangani renderin JSON dan header Content-Type untuk kita
  • Pengecekan method: DRF otomatis raise 405 Method Not Allowed untuk method yang tidak ditangani
  • Exception handling: DRF catch error-error umum dan me-return JSON error response yang rapi

Perbedaan antara Django view biasa dan DRF view itu seperti perbedaan antara menulis raw SQL dan menggunakan ORM milik Django. Kita bisa saja melakukannya dengan cara low-level, tapi DRF memberi kita tool untuk melakukannya dengan lebih rapi.


Decorator @api_view

Decorator @api_view adalah cara untuk mengubah function Python biasa menjadi view yang DRF-aware.

Contoh sederhananya seperti ini:

1
2
3
4
5
6
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(["GET"])
def hello_view(request):
    return Response({"message": "Hello from DRF"})

Mari kita baca baris per baris.

@api_view([“GET”])

Decorator ini menerima list HTTP method yang boleh ditangani oleh view tersebut. Kalau client mengirim request POST ke view ini, DRF akan otomatis me-return 405 Method Not Allowed tanpa kita perlu menuliskan logic tersebut.

1
2
3
@api_view(["GET"])          # hanya GET yang diizinkan
@api_view(["GET", "POST"])  # GET dan POST diizinkan
@api_view(["GET", "PUT", "DELETE"])  # tiga method diizinkan

Anggap saja list ini sebagai whitelist. Apapun yang tidak ada di list akan otomatis ditolak.

def hello_view(request):

Ini adalah function Python biasa. Parameter request di sini bukan HttpRequest polos milik Django. Parameter request adalah object Request milik DRF. Object Request ini membungkus (wrap) object HttpRequest dan menambahkan beberapa kapabilitas tambahan. Kita akan membahas mengenai ini lebih lanjut di bagian berikutnya.

return Response({“message”: “Hello from DRF”})

Response adalah response class milik DRF. Kita memberinya Python dict (atau list, atau value apapun yang JSON-serializable), dan DRF menangani konversinya ke JSON serta men-set header yang benar. Kita tidak perlu memanggil json.dumps() atau men-set Content-Type secara manual.


Menangani Beberapa HTTP Method Sekaligus

Satu function view bisa menangani beberapa HTTP method sekaligus. Kita cek request.method untuk menentukan apa yang harus dilakukan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

@api_view(["GET", "POST"])
def task_list_view(request):
    if request.method == "GET":
        # Menangani pembacaan data
        return Response({"action": "listing tasks"})

    if request.method == "POST":
        # Menangani pembuatan data
        return Response({"action": "creating a task"}, status=status.HTTP_201_CREATED)

Pattern-nya

Pattern yang akan kita gunakan berulang kali adalah:

1
2
3
4
5
6
7
8
@api_view(["GET", "POST"])
def my_list_view(request):
    if request.method == "GET":
        # baca dan return data
        ...
    if request.method == "POST":
        # validasi dan buat data
        ...

Dan untuk detail view (satu item berdasarkan ID):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@api_view(["GET", "PUT", "DELETE"])
def my_detail_view(request, pk):
    # pk adalah primary key dari item yang akan diambil/diupdate/dihapus
    if request.method == "GET":
        # return item-nya
        ...
    if request.method == "PUT":
        # update item-nya
        ...
    if request.method == "DELETE":
        # hapus item-nya
        ...

Parameter pk berasal dari URL pattern. Kita akan menghubungkan ini nanti pada saat membuat urls.py.


Object Request Milik DRF

DRF me-wrap HttpRequest milik Django ke dalam class Request. Object yang sudah di-wrap ini memberi kita beberapa attribute yang berguna.

request.method

String seperti "GET", "POST", "PUT", "PATCH", atau "DELETE". Kita sudah melihat ini di atas.

request.data

Ini salah satu attribute yang paling penting. Isinya adalah body dari request yang masuk, yang sudah di-parse menjadi Python dict.

Dengan Django biasa, kita harus melakukan ini:

1
2
3
import json
body = json.loads(request.body)  # parsing JSON secara manual
title = body.get("title")

Dengan DRF, kita cukup menulis:

1
2
data = request.data  # sudah di-parse otomatis untuk kita
title = data.get("title")

request.data digunakan untuk body JSON, data form, dan data form multipart. DRF akan mendeteksi tipe content-nya dan melakukan parsing sesuai tipenya.

request.query_params

request.query_params adalah query parameter dari URL, bagian ?key=value dari sebuah URL.

Ketika client memanggil GET /tasks/?status=open&priority=high, maka:

1
2
status_filter = request.query_params.get("status")    # "open"
priority_filter = request.query_params.get("priority") # "high"

request.query_params adalah object mirip dict yang bersifat read-only. Dengan Django biasa, ini adalah request.GET, tapi DRF menggunakan request.query_params supaya maksudnya lebih jelas (request.query_params berfungsi untuk semua method, tidak hanya GET).

request.user

request.user adalah user yang sudah terautentikasi yang melakukan request. Kalau user sudah login, ini adalah instance User milik Django. Kalau request-nya anonymous, ini adalah AnonymousUser.

1
2
3
4
if request.user.is_authenticated:
    print(f"Request from: {request.user.username}")
else:
    print("Anonymous request")

Kita tidak akan menggunakan authentication pada latihan kali ini (kita akan set permission menjadi open), tapi kita perlu tahu bahwa attribute ini ada. Ini akan menjadi krusial nanti ketika kita menambahkan authentication.

Tabel Ringkasan attribute Request

AttributeIsinya ApaContoh
request.methodHTTP method sebagai string"GET"
request.dataRequest body yang sudah di-parse{"title": "Buy milk", "priority": "high"}
request.query_paramsQuery parameter dari URL{"status": "open"}
request.userUser yang terautentikasi<User: alice> atau AnonymousUser

Object Response dan Status Code

Response

Class Response milik DRF menerima object Python (dict, list, string, number, None) dan me-render-nya sebagai JSON. Penggunaan dasarnya:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from rest_framework.response import Response

# Return sebuah dict
return Response({"id": 1, "title": "Buy milk"})

# Return sebuah list
return Response([{"id": 1}, {"id": 2}])

# Return dengan status code tertentu
return Response({"detail": "Created."}, status=201)

# Tidak me-return apapun (untuk DELETE)
return Response(status=204)

Status code dengan rest_framework.status

Melakukan hardcode integer seperti 201 bisa saja dilakukan, tapi DRF sudah menyediakan named constant yang membuat kode kita self-documenting:

1
2
3
4
5
6
7
8
from rest_framework import status

status.HTTP_200_OK           # 200
status.HTTP_201_CREATED      # 201
status.HTTP_204_NO_CONTENT   # 204
status.HTTP_400_BAD_REQUEST  # 400
status.HTTP_404_NOT_FOUND    # 404
status.HTTP_403_FORBIDDEN    # 403

Bandingkan dua baris ini. Keduanya menghasilkan hasil yang sama, tapi salah satunya jelas lebih mudah dibaca:

1
2
return Response({"detail": "Not found."}, status=404)                        # acceptable
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)  # preferable

Status code mana yang digunakan dan kapan

SituasiStatus Code yang Digunakan
GET yang berhasil200 OK
POST yang berhasil (item berhasil dibuat)201 Created
DELETE yang berhasil (tidak ada yang perlu di-return)204 No Content
PUT atau PATCH yang berhasil200 OK
Validasi gagal (data dari client tidak valid)400 Bad Request
Item tidak ditemukan404 Not Found
Tidak diizinkan melakukan ini403 Forbidden
HTTP method yang digunakan salah405 Method Not Allowed (DRF menangani ini secara otomatis)

Latihan

Lab ini TIDAK melanjutkan app dari materi sebelumnya. Jangan gunakan ulang app yang sudah ada. Kita akan membangun Task API sederhana dengan endpoint-endpoint berikut:

  • GET /tasks/: menampilkan list semua task
  • POST /tasks/: membuat task baru
  • GET /tasks/<pk>/: mengambil satu task
  • PUT /tasks/<pk>/: update satu task
  • DELETE /tasks/<pk>/: menghapus satu task

Semuanya menggunakan function-based views saja.


Buat app dan daftarkan

1
2
3
cd my-drf-project
source .venv/bin/activate
python manage.py startapp fbv_lab

Tambahkan ke INSTALLED_APPS di config/settings.py:

1
2
3
4
INSTALLED_APPS = [
    # ...apps existing...
    "fbv_lab",
]

Buat model di fbv_lab/models.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.db import models


class Task(models.Model):
    class Priority(models.TextChoices):
        LOW = "low", "Low"
        MEDIUM = "medium", "Medium"
        HIGH = "high", "High"

    title = models.CharField(max_length=120)
    notes = models.TextField(blank=True)
    priority = models.CharField(
        max_length=10,
        choices=Priority.choices,
        default=Priority.MEDIUM,
    )
    is_complete = models.BooleanField(default=False)
    due_date = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

Buat dan apply migrations

1
2
python manage.py makemigrations fbv_lab
python manage.py migrate

Buat serializer di fbv_lab/serializers.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from rest_framework import serializers
from .models import Task


class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = [
            "id",
            "title",
            "notes",
            "priority",
            "is_complete",
            "due_date",
            "created_at",
        ]
        read_only_fields = ["id", "created_at"]

    def validate_title(self, value):
        value = value.strip()
        if len(value) < 3:
            raise serializers.ValidationError("Title must be at least 3 characters.")
        return value

Tidak ada hal yang baru di kode ini. Ini adalah pattern serializer yang sama dengan yang sudah pernah kita pelajari di materi sebelumnya. Layer view men-delegate semua validasi ke serializer.


Buat views di fbv_lab/views.py

Baca setiap view-nya dengan teliti. Pada comment-nya akan dijelaskan mengenai kodenya.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

from .models import Task
from .serializers import TaskSerializer


# ============================================================
# LIST VIEW: GET /tasks/ dan POST /tasks/
# ============================================================

@api_view(["GET", "POST"])
def task_list(request):
    """
    GET  /tasks/: return list semua task
    POST /tasks/: membuat task baru
    """

    if request.method == "GET":
        tasks = Task.objects.all()
        # many=True wajib digunakan saat serialize queryset (multiple objects)
        serializer = TaskSerializer(tasks, many=True)
        return Response(serializer.data)

    if request.method == "POST":
        # request.data berisi JSON body yang sudah di-parse, yang dikirim oleh client
        serializer = TaskSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            # 201 Created adalah status code yang benar ketika resource baru dibuat
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        # 400 Bad Request kalau validasi gagal
        # serializer.errors berisi pesan-pesan error validasi
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


# ============================================================
# DETAIL VIEW: GET, PUT, DELETE /tasks/<pk>/
# ============================================================

@api_view(["GET", "PUT", "DELETE"])
def task_detail(request, pk):
    """
    GET    /tasks/<pk>/: mengambil satu task
    PUT    /tasks/<pk>/: mengganti task secara keseluruhan
    DELETE /tasks/<pk>/: menghapus task
    """

    # Pertama, coba cari task-nya. Kalau tidak ada, return 404.
    # Block ini sama untuk ketiga method, jadi block ini berjalan sebelum if/if/if.
    try:
        task = Task.objects.get(pk=pk)
    except Task.DoesNotExist:
        return Response(
            {"detail": "Not found."},
            status=status.HTTP_404_NOT_FOUND,
        )

    if request.method == "GET":
        # Serialize instance tunggal (tidak perlu many=True untuk single object)
        serializer = TaskSerializer(task)
        return Response(serializer.data)

    if request.method == "PUT":
        # Berikan instance yang sudah ada DAN data barunya
        # DRF akan memanggil update() bukan create() karena instance-nya sudah diberikan
        serializer = TaskSerializer(task, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "DELETE":
        task.delete()
        # 204 No Content: berhasil, tapi tidak ada yang perlu di-return
        return Response(status=status.HTTP_204_NO_CONTENT)

Kenapa try/except Task.DoesNotExist?

Kalau kita memanggil Task.objects.get(pk=pk) dan tidak ada row dengan primary key tersebut, Django akan raise Task.DoesNotExist. Kalau tidak kita catch, Django akan me-return 500 Internal Server Error, dan itu keliru. Kesalahan ini sebenarnya ada di sisi client, karena mereka meminta resource yang tidak ada, jadi response yang benar adalah 404.

Selalu catch DoesNotExist dan return 404. Ini adalah salah satu pattern paling umum di view DRF.


Hubungkan URL-nya

Buat fbv_lab/urls.py (file ini tidak ada secara default, jadi kita perlu buat terlebih dahulu):

1
2
3
4
5
6
7
from django.urls import path
from . import views

urlpatterns = [
    path("tasks/", views.task_list, name="task-list"),
    path("tasks/<int:pk>/", views.task_detail, name="task-detail"),
]

Penjelasan <int:pk>

<int:pk> adalah URL parameter. Ini memberi tahu Django: “catch integer apapun yang ada di bagian URL ini dan berikan ke view sebagai keyword argument bernama pk.”

Jadi kalau client memanggil /tasks/3/, Django akan memanggil task_detail(request, pk=3).

Bagian int: artinya Django hanya akan match integer. Kalau client memanggil /tasks/banana/, Django akan return 404 bahkan sebelum sampai ke view kita.


Daftarkan URL app di project

Buka config/urls.py dan include URL app kita:

1
2
3
4
5
6
7
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/fbv/", include("fbv_lab.urls")),
]

Endpoint kita sekarang menjadi:

  • GET /api/fbv/tasks/
  • POST /api/fbv/tasks/
  • GET /api/fbv/tasks/1/
  • PUT /api/fbv/tasks/1/
  • DELETE /api/fbv/tasks/1/

Tambahkan sample data

1
python manage.py shell
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from datetime import date, timedelta
from fbv_lab.models import Task

Task.objects.all().delete()

today = date.today()

Task.objects.bulk_create([
    Task(title="Set up fbv_lab app", priority="high", due_date=today),
    Task(title="Write views.py", priority="high", due_date=today + timedelta(days=1)),
    Task(title="Test GET list endpoint", priority="medium", due_date=today + timedelta(days=2)),
    Task(title="Test POST endpoint", priority="medium", due_date=today + timedelta(days=3)),
    Task(title="Test 404 handling", priority="low", due_date=today + timedelta(days=4)),
])

print(Task.objects.count())  # 5

Test list endpoint (GET)

Jalankan server:

1
python manage.py runserver

Buka browser dan kunjungi http://127.0.0.1:8000/api/fbv/tasks/

Karena browsable API renderer milik DRF sudah aktif, kita akan melihat HTML page yang rapi menampilkan JSON response-nya. Ini adalah API browser bawaan DRF. Ini hanya untuk keperluan development.

Kita juga bisa melakukan test di Django shell untuk memastikan serializer dan view pipeline-nya berfungsi:

1
python manage.py shell
1
2
3
4
5
6
7
from fbv_lab.models import Task
from fbv_lab.serializers import TaskSerializer

tasks = Task.objects.all()
ser = TaskSerializer(tasks, many=True)
print(len(ser.data))      # 5
print(ser.data[0])        # task pertama sebagai dict

Test full request/response cycle di shell

Django shell tidak melakukan HTTP request yang sebenarnya, tapi kita bisa melakukan simulasi logic view-nya secara langsung. Intinya adalah untuk melatih logic view itu sendiri, tanpa perlu browser, Postman, atau server yang berjalan.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from fbv_lab.models import Task
from fbv_lab.serializers import TaskSerializer
from rest_framework import status

# --- Simulasikan GET list ---
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many=True)
print("Status akan menjadi:", status.HTTP_200_OK)
print("Items:", len(serializer.data))

# --- Simulasikan POST (valid) ---
valid_payload = {
    "title": "Review PR from Steve",
    "notes": "Check his latest iOS serializer integration",
    "priority": "high",
    "is_complete": False,
    "due_date": "2027-08-01",
}
ser = TaskSerializer(data=valid_payload)
print(ser.is_valid())       # True
new_task = ser.save()
print(f"id: {new_task.id}, title: {new_task.title}")

# --- Simulasikan POST (invalid) ---
bad_payload = {"title": "ab", "priority": "urgent"}
ser_bad = TaskSerializer(data=bad_payload)
print(ser_bad.is_valid())   # False
print(ser_bad.errors)       # title terlalu pendek, priority tidak valid

# --- Simulasikan GET detail ---
task = Task.objects.first()
ser_detail = TaskSerializer(task)
print(ser_detail.data)

# --- Simulasikan PUT (full update) ---
update_payload = {
    "title": "Review PR from Steve (updated)",
    "notes": "Checked. Looks good.",
    "priority": "medium",
    "is_complete": True,
    "due_date": "2027-08-01",
}
ser_update = TaskSerializer(task, data=update_payload)
print(ser_update.is_valid()) # True
updated = ser_update.save()
print(updated.title, updated.is_complete)

# --- Simulasikan 404 ---
try:
    missing = Task.objects.get(pk=99999)
except Task.DoesNotExist:
    print("Akan return 404:", status.HTTP_404_NOT_FOUND)

# --- Simulasikan DELETE ---
task_to_delete = Task.objects.last()
task_id = task_to_delete.id
task_to_delete.delete()
print(f"Task {task_id} dihapus. Akan return 204:", status.HTTP_204_NO_CONTENT)
print("Task yang tersisa:", Task.objects.count())

Alur lengkap dari request hingga response

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Client
  |
  | HTTP Request (method + URL + body)
  v
config/urls.py           <-- "app mana yang menangani URL ini?"
  |
fbv_lab/urls.py          <-- "view mana yang menangani path spesifik ini?"
  |
decorator @api_view      <-- membungkus function-nya, mengecek method yang diizinkan
  |
function view            <-- membaca request.method, request.data, request.query_params
  |
serializer               <-- memvalidasi input (POST/PUT) atau memformat output (GET)
  |
model / ORM              <-- membaca dari atau menulis ke database
  |
Response(data, status)   <-- DRF membangun JSON response-nya
  |
  | HTTP Response (status code + JSON body)
  v
Client

Setiap HTTP request ke API kita mengikuti path yang sama persis seperti ini. Saat terjadi masalah, diagram ini akan membantu kita mencari tahu di mana masalahnya.


Tabel perbandingan: Django view biasa vs DRF function-based view

FeatureDjango view biasaDRF @api_view view
JSON parsingManual (json.loads(request.body))Otomatis (request.data)
Query paramsrequest.GETrequest.query_params
JSON responseManual (JsonResponse, set header manual)Response(data)
Method not allowedManual dengan if checkOtomatis 405 dari @api_view
Status codeRaw integerConstant status.HTTP_200_OK
Browsable APITidakYa (saat development)
Integrasi serializerManualNatural

Kesalahan yang sering terjadi

  • Lupa many=True saat serialize queryset. Serialize Task.objects.all() tanpa many=True akan menyebabkan error karena DRF mengharapkan single instance, bukan queryset.

  • Memanggil serializer.save() sebelum serializer.is_valid(). DRF akan raise AssertionError. Selalu panggil is_valid() terlebih dahulu dan cek hasilnya.

  • Me-return status code yang salah pada POST. Pembuatan yang berhasil seharusnya 201 Created, bukan 200 OK. Beberapa client memperlakukan ini secara berbeda.

  • Tidak catch DoesNotExist. Kalau item-nya tidak ditemukan, kita akan mendapat error 500 dibandingkan 404 yang bersih.

  • Mengirim data=request.data pada saat melakukan GET. Di GET, kita melakukan serialize instance untuk output, bukan memvalidasi input. Gunakan TaskSerializer(instance), bukan TaskSerializer(data=...).

  • Tertukar antara tiga signature constructor serializer:

    • TaskSerializer(instance): output (serialize data yang sudah ada)
    • TaskSerializer(data=request.data): input (memvalidasi data yang masuk)
    • TaskSerializer(instance, data=request.data): update (memvalidasi data yang masuk terhadap instance yang sudah ada)

Ringkasan

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@api_view(["GET", "POST"])   -> whitelist method yang diizinkan; method lain otomatis dapat 405
request.data                 -> JSON body yang sudah di-parse dari request yang masuk
request.query_params         -> query parameter dari URL (?key=value)
request.user                 -> user yang terautentikasi (atau AnonymousUser)
Response(data, status=...)   -> DRF membangun JSON response-nya untuk kita
status.HTTP_200_OK           -> named constant untuk status code (lebih baik daripada raw integer)
Task.DoesNotExist            -> selalu tangkap ini di detail view dan return 404
many=True                    -> wajib digunakan saat serialize queryset (multiple objects)
serializer.is_valid()        -> selalu panggil ini sebelum serializer.save()
serializer.errors            -> return ini dengan 400 kalau validasi gagal

Foto cover oleh theofoto dari Unsplash

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