Episode ini membedah cara mengembalikan response dengan berbagai format: JSON, XML, YAML, TOML, dan ProtoBuf. Kalian juga belajar merender HTML template, redirect, melayani file statis, upload file, serta streaming response.

Setelah middleware dipasang dan request berhasil dibaca, saatnya menentukan apa yang dikirim kembali ke klien. Episode 7 ini membedah response rendering: bagaimana Gin mengubah data Go menjadi JSON, XML, YAML, TOML, atau ProtoBuf, bagaimana merender HTML template, dan bagaimana menangani file — statis, upload, maupun streaming.
Kenapa bagian ini penting? Karena kualitas API tidak hanya diukur dari kecepatan routing, tetapi juga dari format response yang konsisten dan cara menangani aset. Endpoint yang mengembalikan format tidak konsisten akan menyulitkan klien, sementara penanganan file yang salah bisa membocorkan path internal atau membebani memori server.
Episode ini juga menjadi jembatan: konsep content negotiation dan streaming akan dipakai lagi saat membahas SSE dan WebSocket di episode 16.
Gin menyediakan method rendering untuk hampir semua format populer. Semua method ini menerima status code sebagai argumen pertama:
type Product struct {
Name string `json:"name" xml:"name"`
Price float64 `json:"price" xml:"price"`
}
p := Product{Name: "Kopi Nusantara", Price: 45000}
c.JSON(200, p)
c.IndentedJSON(200, p)
c.SecureJSON(200, p)
c.XML(200, p)
c.YAML(200, p)
c.TOML(200, p)
c.ProtoBuf(200, protoMessage)Panggilan c.JSON(200, p) adalah yang paling umum untuk REST API. SecureJSON menambahkan prefix untuk mencegah serangan XSSI, sedangkan IndentedJSON menampilkan output yang mudah dibaca manusia. Sejak Gin v1.12, c.TOML(200, p) dan c.BSON(200, p) juga tersedia untuk kebutuhan tertentu.
Gin bisa merender halaman HTML lengkap dengan template engine Go bawaan. Siapkan folder template dan panggil LoadHTMLGlob:
mkdir -p templatesr := gin.New()
r.LoadHTMLGlob("templates/*")
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "Halaman Beranda",
"user": "Arman",
})
})Fungsi r.LoadHTMLGlob("templates/*") memuat semua file di folder templates. Nama template yang dipakai c.HTML(200, "index.html", ...) adalah nama file itu sendiri. Jika kalian memakai file statis untuk template, gunakan LoadHTMLFiles dengan daftar path eksplisit.
Satu endpoint bisa melayani beberapa format sekaligus berdasarkan header Accept klien:
func getUser(c *gin.Context) {
user := gin.H{"id": 1, "name": "Arman"}
switch c.NegotiateFormat(gin.MIMEJSON, gin.MIMEXML, gin.MIMEYAML) {
case gin.MIMEJSON:
c.JSON(200, user)
case gin.MIMEXML:
c.XML(200, user)
case gin.MIMEYAML:
c.YAML(200, user)
default:
c.JSON(200, user)
}
}c.NegotiateFormat(gin.MIMEJSON, ...) mengembalikan format pertama yang cocok dengan header Accept. Jika tidak ada yang cocok, mengembalikan string kosong sehingga kalian bisa menyediakan fallback JSON.
Untuk aset seperti CSS, JavaScript, dan gambar, gunakan method Static:
r.Static("/static", "./public")
r.StaticFile("/favicon.ico", "./public/favicon.ico")
r.StaticFileFS("/assets", "./public", http.Dir("./public"))r.Static("/static", "./public") memetakan semua isi folder ./public ke URL berawalan /static. Directory listing dinonaktifkan secara otomatis, dan Strict-Transport-Security tidak perlu diatur di sini karena ini urusan HTTPS (episode 15).
Untuk mengirim satu file sebagai response, misalnya laporan yang dibangkitkan server:
r.GET("/laporan/:id", func(c *gin.Context) {
c.FileAttachment("./storage/laporan.pdf", "laporan.pdf")
})c.FileAttachment("./storage/laporan.pdf", "laporan.pdf") mengirim file sekaligus mengatur header Content-Disposition agar browser menawarkan dialog download. Gunakan c.File(path) jika ingin file ditampilkan inline di browser.
Untuk upload, form HTML memakai enctype="multipart/form-data". Di sisi handler:
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "file wajib dikirim"})
return
}
dst := "./storage/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "upload berhasil", "name": file.Filename})
})c.FormFile("file") membaca file dari form multipart, lalu c.SaveUploadedFile(file, dst) menyimpannya ke disk. Batasi ukuran upload dengan r.MaxMultipartMemory = 8 << 20 untuk mencegah memori jebol.
Tes dengan curl:
curl -X POST -F "file=@laporan.txt" http://localhost:8080/uploadUntuk response besar tanpa menahan semuanya di memori, gin menyediakan c.DataFromReader:
file, err := os.Open("./data/besar.csv")
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
defer file.Close()
info, _ := file.Stat()
c.DataFromReader(200, info.Size(), "text/csv", file, nil)c.DataFromReader(200, ...) menulis body secara bertahap dari sebuah io.Reader. Dengan cara ini file sebesar apa pun tidak pernah dimuat utuh ke memori. Untuk streaming satu arah realtime, Gin juga memiliki c.SSEvent yang akan dibahas mendalam di episode 16.
Inti yang harus dibawa pulang:
c.JSON, c.XML, c.YAML, c.TOML, dan c.ProtoBuf untuk response berformat.LoadHTMLGlob + c.HTML untuk merender halaman HTML template.c.Redirect untuk pengalihan; c.NegotiateFormat untuk content negotiation.r.Static dan c.FileAttachment untuk file statis dan download.c.FormFile + c.SaveUploadedFile untuk menerima upload.c.DataFromReader untuk streaming response tanpa memuat semua ke memori.Di episode 8 selanjutnya kita akan membedah struktur proyek & clean architecture — menyusun kode menjadi handler, service, dan repository, memakai folder internal, dependency injection sederhana, dan memisahkan router setup agar mudah diuji dan di-scale.