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:
| Pendekatan | Definisinya | Kapan Digunakan |
|---|---|---|
| Function-Based Views (FBV) | Function Python biasa yang di-decorate dengan @api_view | Endpoint sederhana, belajar, custom logic |
| Class-Based Views (CBV) | Class yang inherit dari APIView | Struktur yang reusable, endpoint yang lebih kompleks |
| Generic Views / ViewSets | View class bawaan DRF yang sudah siap pakai | CRUD 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:
| |
Method memberi tahu server apa yang ingin dilakukan client:
| HTTP Method | Yang Diinginkan Client | Penggunaan 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:
| |
Status code memberi tahu ke client status dari request yang mereka kirimkan:
| Range | Artinya | Contoh Umum |
|---|---|---|
| 2xx | Sukses | 200 OK, 201 Created, 204 No Content |
| 4xx | Kesalahan pada Client | 400 Bad Request, 404 Not Found, 403 Forbidden |
| 5xx | Kesalahan pada Server | 500 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:
| |
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.bodysecara manual - Kita perlu men-set header
Content-Type: application/jsonsecara 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 kitarequest.query_params: query parameter URL seperti?status=opensudah otomatis di-parse (dipecah dan diubah menjadi data yang bisa langsung dipakai)request.user: user yang sudah terautentikasi akan otomatis tersediaResponse(data): DRF menangani renderin JSON dan headerContent-Typeuntuk kita- Pengecekan method: DRF otomatis raise
405 Method Not Alloweduntuk 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:
| |
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.
| |
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:
| |
Pattern-nya
Pattern yang akan kita gunakan berulang kali adalah:
| |
Dan untuk detail view (satu item berdasarkan ID):
| |
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:
| |
Dengan DRF, kita cukup menulis:
| |
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:
| |
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.
| |
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
| Attribute | Isinya Apa | Contoh |
|---|---|---|
request.method | HTTP method sebagai string | "GET" |
request.data | Request body yang sudah di-parse | {"title": "Buy milk", "priority": "high"} |
request.query_params | Query parameter dari URL | {"status": "open"} |
request.user | User 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:
| |
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:
| |
Bandingkan dua baris ini. Keduanya menghasilkan hasil yang sama, tapi salah satunya jelas lebih mudah dibaca:
| |
Status code mana yang digunakan dan kapan
| Situasi | Status Code yang Digunakan |
|---|---|
| GET yang berhasil | 200 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 berhasil | 200 OK |
| Validasi gagal (data dari client tidak valid) | 400 Bad Request |
| Item tidak ditemukan | 404 Not Found |
| Tidak diizinkan melakukan ini | 403 Forbidden |
| HTTP method yang digunakan salah | 405 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 taskPOST /tasks/: membuat task baruGET /tasks/<pk>/: mengambil satu taskPUT /tasks/<pk>/: update satu taskDELETE /tasks/<pk>/: menghapus satu task
Semuanya menggunakan function-based views saja.
Buat app dan daftarkan
| |
Tambahkan ke INSTALLED_APPS di config/settings.py:
| |
Buat model di fbv_lab/models.py
| |
Buat dan apply migrations
| |
Buat serializer di fbv_lab/serializers.py
| |
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.
| |
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):
| |
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:
| |
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
| |
| |
Test list endpoint (GET)
Jalankan server:
| |
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:
| |
| |
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.
| |
Alur lengkap dari request hingga response
| |
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
| Feature | Django view biasa | DRF @api_view view |
|---|---|---|
| JSON parsing | Manual (json.loads(request.body)) | Otomatis (request.data) |
| Query params | request.GET | request.query_params |
| JSON response | Manual (JsonResponse, set header manual) | Response(data) |
| Method not allowed | Manual dengan if check | Otomatis 405 dari @api_view |
| Status code | Raw integer | Constant status.HTTP_200_OK |
| Browsable API | Tidak | Ya (saat development) |
| Integrasi serializer | Manual | Natural |
Kesalahan yang sering terjadi
Lupa
many=Truesaat serialize queryset. SerializeTask.objects.all()tanpamany=Trueakan menyebabkan error karena DRF mengharapkan single instance, bukan queryset.Memanggil
serializer.save()sebelumserializer.is_valid(). DRF akan raiseAssertionError. Selalu panggilis_valid()terlebih dahulu dan cek hasilnya.Me-return status code yang salah pada POST. Pembuatan yang berhasil seharusnya
201 Created, bukan200 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.datapada saat melakukan GET. Di GET, kita melakukan serialize instance untuk output, bukan memvalidasi input. GunakanTaskSerializer(instance), bukanTaskSerializer(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
| |
