Controller adalah lapisan yang menerjemahkan permintaan HTTP menjadi respons.
Topik ini membahas controller JSON dan controller HTML, beserta alur param
dari jalur maupun query sampai ke fungsi aksi. Setelah membaca, fungsi aksi
dapat ditulis dengan json/2 dan html/2, pola render/2 pada proyek nyata
dipahami, dan perbedaannya dengan demo mandiri juga diketahui.
Fungsi aksi menerima conn dan param#
Sebuah controller Phoenix adalah modul biasa yang fungsinya dipanggil
oleh router sebagai action. Setiap fungsi aksi menerima dua argumen, yaitu
conn berisi keadaan permintaan dan params berisi param (params), yakni
data yang dikirim pemanggil bersama permintaan. Param digabungkan dari tiga
sumber menjadi satu map: segmen dinamis pada rute, seperti :id pada
get "/api/catatan/:id", string query, dan tubuh permintaan pada metode POST.
Karena digabung, params["id"] mengembalikan nilai yang sama, baik dari
jalur maupun dari query.
Argumen params adalah map dengan kunci string, sehingga pencocokan pola pada
tanda tangan fungsi dapat digunakan untuk memaksa bentuk param yang diharapkan.
Fungsi show(conn, %{"id" => id}) hanya cocok apabila param id ada, dan itu
menjadi bentuk penjagaan yang ringkas. Sebaliknya, argumen dengan pola
_params menandakan aksi yang tidak membutuhkan param sama sekali.
Kedua argumen tersebut mengikuti satu kontrak yang sama di seluruh Phoenix.
Fungsi aksi mewarisi keadaan conn dari pipeline sebelumnya, lalu keadaan
hasil kerjanya mengalir kembali setelah respons dikirim. Karena itu, aksi yang
ditulis rapi biasanya pendek: membaca param, memanggil context, lalu
mengirim respons dengan json/2, html/2, atau render/2.
Respons JSON dengan json/2#
Fungsi json/2 menyandikan map Elixir menjadi JSON, menyetel header
content-type ke application/json, dan mengirimkan respons dalam satu
panggilan. Fungsi ini mengembalikan conn yang sudah selesai, dan conn itulah
yang menjadi nilai akhir fungsi aksi. Demo berikut menggunakan satu skrip utuh yang
memuat router, controller, tiga permintaan, dan satu respons HTML.
Mix.install([{:phoenix, "~> 1.8"}, {:jason, "~> 1.4"}])
Logger.configure(level: :warning)
defmodule CatatanController do
use Phoenix.Controller, formats: [:json, :html]
def index(conn, params) do
data = [%{id: 1, judul: "Puisi hujan"}]
json(conn, %{data: data, q: params["q"]})
end
def show(conn, %{"id" => id}) do
json(conn, %{id: id, judul: "Puisi hujan"})
end
def unduh(conn, _params) do
html(conn, "<h1>Catatan belajar</h1>")
end
end
defmodule CatatanRouter do
use Phoenix.Router
pipeline :api do
plug :accepts, ["json"]
end
pipeline :browser do
plug :accepts, ["html"]
end
scope "/api" do
pipe_through :api
get "/catatan", CatatanController, :index
get "/catatan/:id", CatatanController, :show
end
scope "/" do
pipe_through :browser
get "/catatan/unduh", CatatanController, :unduh
end
end
# permintaan dengan query param
conn =
Phoenix.ConnTest.build_conn(:get, "/api/catatan", %{"q" => "puisi"})
|> CatatanRouter.call(CatatanRouter.init([]))
IO.inspect(conn.status, label: "status")
IO.inspect(Jason.decode!(conn.resp_body), label: "isi")
# permintaan dengan param jalur
conn =
Phoenix.ConnTest.build_conn(:get, "/api/catatan/3", nil)
|> CatatanRouter.call(CatatanRouter.init([]))
IO.inspect(conn.status, label: "status show")
IO.inspect(Jason.decode!(conn.resp_body), label: "isi show")
# respons HTML
conn =
Phoenix.ConnTest.build_conn(:get, "/catatan/unduh", nil)
|> CatatanRouter.call(CatatanRouter.init([]))
IO.inspect(conn.status, label: "status html")
IO.inspect(conn.resp_body, label: "isi html")Jalankan skripnya dengan elixir catatan_controller.exs. Tiga permintaan
menghasilkan keluaran berikut.
status: 200
isi: %{"data" => [%{"id" => 1, "judul" => "Puisi hujan"}], "q" => "puisi"}
status show: 200
isi show: %{"id" => "3", "judul" => "Puisi hujan"}
status html: 200
isi html: "<h1>Catatan belajar</h1>"Keluaran tersebut memuat dua fakta yang perlu digarisbawahi. Pertama, query
param q dari build_conn(:get, "/api/catatan", %{"q" => "puisi"}) sampai ke
aksi index sebagai params["q"]. Kedua, param jalur :id selalu berupa
string, sehingga %{"id" => "3"} berisi "3", bukan angka. Konversi ke
bilangan bulat dilakukan sendiri dengan String.to_integer/1 pada waktu yang
tepat, biasanya di context.
Fungsi json/2 berhenti setelah satu kali pengiriman. Penambahan header atau
status setelah memanggilnya tidak berpengaruh, karena tubuh respons sudah
ditetapkan. Apabila status berbeda dari 200 diperlukan, fungsi put_status/2
dipanggil sebelum json/2, dan urutan ini berlaku untuk fungsi respons lain
juga.
Respons HTML dengan html/2#
Fungsi html/2 mengirimkan string HTML sebagai tubuh respons dan menyetel
header content-type ke text/html; charset=utf-8. Aksi unduh pada demo di
atas menggunakan fungsi ini dan tetap melewati pipeline :browser yang menegaskan
format HTML melalui plug :accepts, ["html"]. Untuk halaman statis sederhana,
html/2 sudah cukup dan tidak menuntut berkas template apa pun.
Perbedaan html/2 dengan render/2 terletak pada sumber tubuh respons.
html/2 menerima string yang sudah jadi, sedangkan render/2 menyusun string
tersebut dari template HEEx beserta nilai yang dikirim. Dua fungsi tersebut
sama-sama berakhir sebagai conn dengan tubuh respons, sehingga pemilihan
di antara keduanya adalah perkara sumber isi, bukan perkara arsitektur. Karena
itu, html/2 juga wajar digunakan untuk respons kecil di luar halaman, misalnya
tubuh teks sederhana atau isi berkas yang sudah tersusun.
Pola render/2 pada proyek nyata#
Proyek hasil mix phx.new menulis controller dengan render/2. Controller
menyebut nama template, lalu Phoenix mencari modul view yang cocok secara
otomatis berdasarkan nama controller. Selanjutnya, template HEEx pada modul view
tersebut merender halaman. Potongan berikut diambil dari proyek Phoenix 1.8:
# tidak-dijalankan (fragmen proyek, bukan skrip utuh)
defmodule NamaProyekWeb.PageController do
use NamaProyekWeb, :controller
def home(conn, _params) do
render(conn, :home)
end
end# tidak-dijalankan (fragmen proyek, bukan skrip utuh)
defmodule NamaProyekWeb.PageHTML do
use NamaProyekWeb, :html
embed_templates "page_html/*"
endMakro use NamaProyekWeb, :controller menyiapkan banyak hal sekaligus, salah
satunya pemetaan format ke modul view. Pada render(conn, :home), Phoenix
meneruskan pekerjaan ke PageHTML dan template home.html.heex di dalam
folder page_html. Konvensi penamaannya tegas: controller bernama
PageController dipadukan dengan view bernama PageHTML, dan hubungan ini
diturunkan dari nama modul tanpa konfigurasi tambahan. Pembagian ini menjaga
controller tetap ramping: logika pemilihan data ada di controller, sedangkan
penyusunan tampilan ada di template. Detail template HEEx dibahas pada topik
Template dan layout.
Apabila modul view perlu ditentukan secara eksplisit, fungsi put_view/2
dipanggil sebelum render/2. Pola ini berguna untuk merender view yang tidak
cocok dengan nama controller, misalnya modul view bersama untuk beberapa
controller.
Kesalahan umum#
Kesalahan pertama adalah menganggap param jalur berupa bilangan. Nilai
params["id"] selalu string, sehingga perbandingan langsung dengan bilangan bulat
selalu gagal dan query database dapat salah sasaran. Perbaikannya,
konversi secara eksplisit dengan String.to_integer/1, atau tangkap error
ArgumentError apabila nilai tidak layak dikonversi.
Kesalahan kedua adalah lupa mengembalikan conn. Fungsi aksi wajib
mengembalikan conn yang sudah berisi respons, dan hasil json/2, html/2,
maupun render/2 semuanya adalah conn. Fungsi aksi yang berakhir dengan
perhitungan lain, misalnya map, membuat Phoenix melempar error yang
menyatakan bahwa fungsi aksi harus mengembalikan Plug.Conn. Pola pipa yang
berakhir pada json(conn, data) menghindari kesalahan ini.
Kesalahan ketiga adalah mencampur format tanpa garis batas. Aksi pada scope
pipe_through :api yang mengirim HTML akan membingungkan konsumen API, dan
sebaliknya, JSON yang dikirim pada rute halaman HTML rusak di browser. Tata format pada
tingkat scope, dan pastikan :accepts berjalan paling depan di setiap
pipeline.
Ringkasan#
- Fungsi aksi menerima
conndanparams, lalu wajib mengembalikanconnyang sudah membawa respons. - Param digabung dari jalur, query, dan tubuh permintaan, dengan kunci berupa
string, termasuk param jalur seperti
:id. json/2mengirim JSON danhtml/2mengirim string HTML, keduanya menyetel headercontent-typesecara otomatis.- Proyek nyata menggunakan
render/2yang merujuk modul view dan template HEEx, sedangkanput_view/2menyebut modul view secara eksplisit apabila perlu.
Referensi#
- Controllers — Phoenix Framework — Panduan controller, action, dan render pada Phoenix.
- Plug.Conn — Plug
— Struktur
connyang dikirim dan diterima setiap fungsi aksi. - Phoenix.ConnTest — Phoenix Framework
— Dokumentasi
build_conn/3yang digunakan demo untuk membangun permintaan.