M
Mr Sugiarto
Developer
30 Aug 2026 3 min read

Endpoint GET /api/v1/posts kita sejauh ini selalu mengembalikan semua post sekaligus. Baik-baik saja untuk data dummy, tapi kalau nanti sudah ada ribuan post, ini jelas tidak efisien — response jadi besar dan query database jadi berat. Di part ini kita tambahkan pagination dan pencarian sederhana.

Membaca Query Parameter

Fiber menyediakan c.Query("nama", "default") untuk membaca query string dengan nilai default kalau tidak dikirim. Update PostHandler.Index:

func (h *PostHandler) Index(c *fiber.Ctx) error {
	page, err := strconv.Atoi(c.Query("page", "1"))
	if err != nil || page < 1 {
		page = 1
	}

	perPage, err := strconv.Atoi(c.Query("per_page", "10"))
	if err != nil || perPage < 1 || perPage > 100 {
		perPage = 10
	}

	search := c.Query("search", "")

	query := h.DB.Model(&models.Post{})
	if search != "" {
		like := "%" + search + "%"
		query = query.Where("title LIKE ? OR body LIKE ?", like, like)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return response.Error(c, fiber.StatusInternalServerError, "gagal mengambil data post")
	}

	var posts []models.Post
	offset := (page - 1) * perPage
	if err := query.Order("id desc").Offset(offset).Limit(perPage).Find(&posts).Error; err != nil {
		return response.Error(c, fiber.StatusInternalServerError, "gagal mengambil data post")
	}

	totalPages := int(math.Ceil(float64(total) / float64(perPage)))

	return c.JSON(fiber.Map{
		"message": "berhasil mengambil data post",
		"data":    posts,
		"meta": fiber.Map{
			"page":        page,
			"per_page":    perPage,
			"total":       total,
			"total_pages": totalPages,
		},
	})
}

Beberapa hal penting:

  • page dan per_page divalidasi manual (bukan lewat go-playground/validator, karena ini query parameter, bukan body request) — kalau nilainya tidak valid atau negatif, fallback ke default.
  • per_page dibatasi maksimal 100 supaya client tidak bisa minta seluruh tabel sekaligus lewat per_page=999999.
  • query := h.DB.Model(&models.Post{}) dibuat sekali, lalu dipakai ulang untuk Count (total sebelum pagination) dan Find (data untuk halaman ini) — supaya filter search konsisten di kedua query.
  • Offset dan Limit adalah cara GORM melakukan pagination di level SQL (LIMIT ... OFFSET ...), jauh lebih efisien daripada ambil semua data lalu di-slice di sisi aplikasi.

Testing

# Ambil 3 post pertama
curl "http://127.0.0.1:3000/api/v1/posts?page=1&per_page=3"

# Halaman kedua
curl "http://127.0.0.1:3000/api/v1/posts?page=2&per_page=3"

# Cari post yang mengandung kata tertentu di title atau body
curl "http://127.0.0.1:3000/api/v1/posts?search=GoFiber"

Response sekarang punya bagian meta di samping data:

{
  "message": "berhasil mengambil data post",
  "data": [ ... ],
  "meta": {
    "page": 1,
    "per_page": 2,
    "total": 15,
    "total_pages": 8
  }
}

Field meta ini yang biasanya dipakai frontend untuk menampilkan navigasi halaman (misalnya tombol "Next"/"Previous" atau nomor halaman).

Endpoint list Post kita sekarang siap menangani data dalam jumlah besar. Di part berikutnya kita hubungkan Post dengan User sebagai penulisnya (author) — sekaligus menutup celah otorisasi yang sudah kita catat sejak Part 9: siapapun yang login masih bisa edit/hapus post siapa saja.

M
Mr Sugiarto

Developer

Bagian dari Series: GoFiber Dasar - Belajar Fundamental Lewat REST API Blog

Belajar konsep-konsep dasar GoFiber (routing, handler, GORM, MySQL, JWT auth) dengan membangun REST API blog sederhana dari nol - lengkap dengan auten...

Lihat Series Lengkap