Belajar Chi - Responses, JSON & Static Files
Episode 7 of 23

Belajar Chi - Responses, JSON & Static Files

Episode ini fokus pada sisi respons: helper JSON yang rapi, rendering HTML, redirect, dan streaming respons. Kalian juga belajar melayani file statis dengan http.FileServer, menangani upload file, dan menulis custom response writer untuk kasus khusus.

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

Pendahuluan

Handler yang baik tidak hanya membaca request — ia juga mengirimkan respons yang benar. Episode 7 mengajarkan seluruh sisi output: JSON yang konsisten, HTML, redirect, streaming, dan file statis.

Banyak aplikasi Go gagal karena respons JSON-nya tidak seragam: kadang status salah, kadang content type lupa di-set. Episode ini memberikan pola yang konsisten untuk dipakai di seluruh project kalian.

Helper JSON yang Rapi

Mengapa Butuh Helper

Menulis empat baris JSON encoding di setiap handler mengundang inkonsistensi. Buat helper sekali, pakai di mana-mana:

Helper respons JSON
func writeJSON(w http.ResponseWriter, status int, data any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(data)
}

writeJSON(w, http.StatusCreated, user) men-set header, status, lalu encode dalam satu panggilan. Parameter status int dan data any membuat helper ini menangani sukses maupun error.

Memakai Helper di Handler

Handler dengan helper
func createUserHandler(w http.ResponseWriter, req *http.Request) {
    var input User
    if err := json.NewDecoder(req.Body).Decode(&input); err != nil {
        writeJSON(w, http.StatusBadRequest,
            map[string]string{"error": "body tidak valid"})
        return
    }
    writeJSON(w, http.StatusCreated, input)
}

json.NewDecoder(req.Body).Decode(&input) membaca body JSON dari request, lalu helper mengirimkan respons 201 Created dengan data hasil decode.

HTML, Redirect, dan Streaming

Rendering HTML

Untuk halaman sederhana, gunakan html/template:

Rendering HTML
tpl := template.Must(template.ParseFiles("templates/home.html"))
 
r.Get("/", func(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    tpl.Execute(w, map[string]string{"Nama": "Budi"})
})

tpl.Execute(w, data) menulis HTML hasil template langsung ke ResponseWriter. Hindari fmt.Fprintf untuk data tak terpercaya karena rawan injection — html/template meng-escape otomatis.

Redirect

Redirect
r.Get("/old", func(w http.ResponseWriter, req *http.Request) {
    http.Redirect(w, req, "/new", http.StatusMovedPermanently)
})

http.Redirect(w, req, "/new", http.StatusMovedPermanently) mengirim status 301 dan header Location: /new. Browser dan curl akan mengikuti otomatis.

Streaming Respons

Untuk data yang terus mengalir, aktifkan flush:

Streaming chunk
r.Get("/stream", func(w http.ResponseWriter, req *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "streaming tidak didukung",
            http.StatusInternalServerError)
        return
    }
    for i := 0; i < 5; i++ {
        fmt.Fprintf(w, "chunk %d\n", i)
        flusher.Flush()
        time.Sleep(500 * time.Millisecond)
    }
})

flusher.Flush() memaksa data terkirim ke klien sebelum handler selesai — pola dasar Server-Sent Events yang akan kita bangun di episode 16.

Static Files

http.FileServer dan StripPrefix

Layani direktori statis dengan kombinasi klasik:

Melayani file statis
fileServer := http.FileServer(http.Dir("./static"))
r.Handle("/static/*", http.StripPrefix("/static", fileServer))

http.StripPrefix("/static", fileServer) membuang prefix /static dari request sehingga /static/css/app.css membaca file ./static/css/app.css. Pola r.Handle("/static/*", ...) menangkap seluruh sub-path.

Alternatif dengan Mount

Pendekatan yang setara dan lebih ringkas:

Mount file server
r.Mount("/static", http.StripPrefix("/static",
    http.FileServer(http.Dir("./static"))))

r.Mount("/static", handler) menempelkan file server pada prefix — hasilnya sama dengan pola Handle, hanya ditulis lebih pendek.

Upload File

Multipart Form

Terima file dari klien dengan ParseMultipartForm:

Handler upload
r.Post("/upload", func(w http.ResponseWriter, req *http.Request) {
    req.ParseMultipartForm(10 << 20)
    file, handler, err := req.FormFile("foto")
    if err != nil {
        http.Error(w, "file tidak ditemukan", http.StatusBadRequest)
        return
    }
    defer file.Close()
 
    dst, _ := os.Create("./uploads/" + handler.Filename)
    defer dst.Close()
    io.Copy(dst, file)
 
    writeJSON(w, http.StatusOK,
        map[string]string{"message": "upload sukses"})
})

req.FormFile("foto") mengambil file dari field foto pada body multipart. Batas 10 << 20 membatasi ukuran body maksimal 10 MiB.

Custom Response Writer

Membungkus ResponseWriter

Kadang kalian perlu mengintip apa yang ditulis handler — misalnya untuk status code logging:

Custom response writer
type statusWriter struct {
    http.ResponseWriter
    status int
}
 
func (sw *statusWriter) WriteHeader(code int) {
    sw.status = code
    sw.ResponseWriter.WriteHeader(code)
}

statusWriter membungkus ResponseWriter asli dan merekam status code. chi bahkan menyediakan middleware.WrapResponseWriter untuk tujuan ini — kita akan pakai lagi di episode 11 dan 20.

Penutup

Inti yang harus dibawa pulang:

  • Helper writeJSON membuat respons JSON konsisten di semua handler.
  • html/template meng-escape otomatis; http.Redirect untuk redirect.
  • flusher.Flush() membuka jalan streaming.
  • http.FileServer plus StripPrefix melayani static files.
  • Upload memakai req.FormFile dan batasan ukuran body.
  • Custom response writer merekam status untuk kebutuhan logging.

Di episode 8 selanjutnya kita naik satu level: struktur project dan clean architecture — memisahkan handler, service, dan repository, memakai layout internal, dependency injection, dan mendefinisikan router sebagai fungsi untuk kemudahan testing.

Belajar Chi - Responses, JSON & Static Files | Belajar Chi