Episode ini membedah sistem routing chi: method routing, path params dengan kurung kurawal, wildcard untuk sisa path, dan regex pattern untuk validasi param. Kalian juga belajar menangani NotFound dan MethodNotAllowed serta urutan matching yang menentukan route mana yang terpilih.

Ini episode paling inti dari seluruh series: routing, params, dan patterns. Di sinilah kalian belajar bagaimana URL diterjemahkan menjadi handler — mulai dari deklarasi r.Get dan r.Post, penangkapan nilai dinamis di tengah path, sampai penanganan route yang tidak ditemukan.
Setelah episode ini, kalian tidak akan lagi menulis route yang datar dan repetitif. Setiap URL bisa menangkap data, memvalidasi format, dan menangani fallback dengan rapi.
chi menyediakan helper untuk setiap method HTTP:
r.Get("/users", listUsers)
r.Post("/users", createUser)
r.Put("/users/{id}", updateUser)
r.Delete("/users/{id}", deleteUser)r.Post("/users", createUser) hanya melayani method POST; request GET ke path yang sama akan masuk ke handler MethodNotAllowed (dibahas di bawah). Ini REST yang bersih tanpa percabangan method di dalam handler.
Untuk method yang tidak punya helper — misalnya PATCH atau OPTIONS — pakai r.Method:
r.Method(http.MethodPatch, "/users/{id}", patchUser)
r.Method(http.MethodOptions, "/users", corsPreflight)r.Method(http.MethodPatch, "/users/{id}", patchUser) menerima nama method sebagai string pertama dan handler sebagai argumen terakhir.
Parameter dinamis ditulis dengan kurung kurawal di dalam pola:
r.Get("/users/{id}", func(w http.ResponseWriter, req *http.Request) {
id := chi.URLParam(req, "id")
w.Write([]byte("user " + id))
})chi.URLParam(req, "id") membaca nilai yang tertangkap oleh pola {id}. Saat request datang ke /users/42, variabel id bernilai "42".
Penting untuk diingat: di chi v5, param bersifat wajib. Route /users/{id} tidak akan mencocokkan /users — untuk itu kalian harus mendaftarkan route /users terpisah. Ini penyederhanaan yang disengaja dibanding versi 4 yang punya syntax param opsional.
Batasilah format param dengan regex di dalam kurung kurawal:
r.Get("/users/{id:[0-9]+}", numericUser)
r.Get("/articles/{slug:[a-z0-9-]+}", articleBySlug)Pola {id:[0-9]+} hanya mencocokkan angka. Request /users/abc tidak akan masuk handler ini — ia jatuh ke NotFound, atau ke route lain yang lebih cocok.
Wildcard menangkap seluruh sisa path, berguna untuk file dan prefix dinamis:
r.Get("/files/{path:*}", func(w http.ResponseWriter, req *http.Request) {
path := chi.URLParam(req, "path")
w.Write([]byte("file: " + path))
}){path:*} mencocokkan /files/a/b/c.txt dengan nilai a/b/c.txt. chi.URLParam(req, "path") mengembalikan seluruh string tersebut, termasuk garis miring di dalamnya.
r.Get("/users/{id}", handlerUser)
r.Get("/users/{id:[0-9]+}", handlerUserNumeric)
r.Get("/files/{path:*}", handlerFile)
r.Post("/users", handlerCreateUser)Perhatikan cara chi memilih: regex yang lebih spesifik mengalahkan param biasa. handlerUserNumeric akan menangkap /users/123, sementara handlerUser menangkap /users/abc.
Saat tidak ada pola yang cocok, chi memanggil handler NotFound. Saat path cocok tapi method tidak, chi memanggil MethodNotAllowed:
r.NotFound(func(w http.ResponseWriter, req *http.Request) {
http.Error(w, "halaman tidak ditemukan", http.StatusNotFound)
})
r.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) {
http.Error(w, "method tidak diizinkan", http.StatusMethodNotAllowed)
})r.NotFound(handler) dan r.MethodNotAllowed(handler) menggantikan pesan bawaan dengan respons JSON atau halaman custom kalian sendiri.
curl -i http://localhost:8080/users/42
curl -i http://localhost:8080/tidak-ada
curl -i -X DELETE http://localhost:8080/users/42curl -i -X DELETE http://localhost:8080/users/42 menguji method yang tidak terdaftar — kalian harus melihat status 405 Method Not Allowed dengan pesan custom.
Pemahaman urutan matching menyelamatkan kalian dari bug misterius:
{path:*} selalu menjadi pilihan terakhir untuk sebuah segmen.Jadi pola r.Get("/users/new", ...) dan r.Get("/users/{id}", ...) bisa hidup berdampingan: request ke /users/new diarahkan ke handler literal, sementara /users/7 masuk ke param handler.
Inti yang harus dibawa pulang:
r.Get, r.Post, r.Put, r.Delete; method lain via r.Method.{id} dibaca dengan chi.URLParam(req, "id").{id:[0-9]+} membatasi format; wildcard {path:*} menangkap sisa path.r.NotFound dan r.MethodNotAllowed menangani fallback.Di episode 5 selanjutnya kita akan mengorganisasi route dalam skala lebih besar: subrouters, groups, dan mounting — memisahkan resource dengan r.Route, berbagi middleware dengan r.Group, dan memasang sub-app dengan r.Mount.