Belajar GraphQL - Implementasi Pagination (Offset, Cursor, Relay)
Episode 12 of 51

Belajar GraphQL - Implementasi Pagination (Offset, Cursor, Relay)

Episode 12 membahas pagination di GraphQL: mengapa pagination penting, offset-based pagination dengan limit dan skip, cursor-based pagination dengan stable cursor, Relay Connection Specification dengan edge node dan pageInfo, hingga best practices ukuran halaman dan caching.

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

Pendahuluan

Mengembalikan seluruh daftar data dalam satu response adalah resep bencana. Episode 12 membahas pagination — teknik mengambil data dalam potongan-potongan terkontrol — yang wajib ada pada hampir semua field list di API production.

Kita akan membandingkan offset-based dan cursor-based pagination, membedah Relay Connection Specification yang menjadi standar de facto, lalu mempelajari implementasi dan best practices-nya, termasuk ukuran halaman default dan batas maksimum.

Mengapa Pagination Penting

Tanpa pagination, daftar jutaan item dikirim sekaligus: response raksasa, database terbebani, dan aplikasi mobile membuang bandwidth. Pagination menyelesaikan tiga masalah:

  • Performa: query lebih cepat karena data diambil dalam potongan.
  • User experience: UI bisa menampilkan konten bertahap, cocok untuk infinite scroll.
  • Resource management: server dan network tidak kewalahan oleh satu request besar.

Aturan praktis industri: semua field list wajib memiliki pagination, kecuali list yang dijamin kecil dan berbatas.

Offset-Based Pagination

Limit dan Skip

Pola paling sederhana: limit menentukan ukuran halaman, skip menentukan berapa item yang dilewati.

Offset pagination
type Query {
  posts(limit: Int = 20, skip: Int = 0): [Post!]!
}
JSResolver offset pagination
Query: {
  posts: (_, args, ctx) =>
    ctx.db.posts.findMany({ take: args.limit, skip: args.skip }),
},

Keunggulannya sederhana dan intuitif, serta mudah mencari total item. Kelemahannya muncul saat data berubah: ketika item baru disisipkan di tengah, halaman berikutnya akan bergeser — item terlewat atau terduplikasi. Untuk feed dinamis, pola ini kurang cocok.

Cursor-Based Pagination

Konsep Cursor

Cursor adalah penanda posisi yang stabil dalam kumpulan data. Alih-alih menghitung "lewati 20 item", server menerima "mulai dari setelah item ini":

Cursor pagination
type Query {
  posts(after: String, first: Int = 20): PostConnection!
}
JSResolver cursor pagination
Query: {
  posts: async (_, args, ctx) => {
    const decoded = args.after ? Buffer.from(args.after, "base64").toString() : null;
    const cursor = decoded ? { id: Number(decoded) } : undefined;
    const posts = await ctx.db.posts.findMany({
      take: args.first + 1,
      ...(cursor && { cursor, skip: 1 }),
    });
    return {
      edges: posts.slice(0, args.first).map((p) => ({ node: p, cursor: encodeCursor(p.id) })),
      pageInfo: { hasNextPage: posts.length > args.first },
    };
  },
},

Perhatikan pola penting: ambil first + 1 item untuk menentukan hasNextPage. Cursor di-encode sebagai base64 dari id unik — ini membuat cursor stable: walau ada item baru masuk, halaman berikutnya tetap mulai dari posisi yang benar. Itulah keunggulan utama cursor dibanding offset.

Relay Connection Specification

Edge, Node, dan PageInfo

Relay Connection Specification adalah standar dari Relay (episode 26) yang kini dipakai luas. Strukturnya terdiri dari:

  • connection dengan field edges dan pageInfo.
  • edge yang membungkus node dan cursor masing-masing item.
  • pageInfo dengan hasNextPage, hasPreviousPage, serta startCursor dan endCursor.
Connection specification
type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}
 
type PostEdge {
  node: Post!
  cursor: String!
}
 
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
 
type Query {
  posts(first: Int = 20, after: String): PostConnection!
  postsBackward(last: Int = 20, before: String): PostConnection!
}

Mengapa Relay spec penting? Karena client framework memahami struktur ini secara universal: infinite scroll, tombol previous, dan refresh daftar bisa dibangun tanpa mengenal detail schema masing-masing server. Cursor juga mendukung navigasi maju (after) dan mundur (before).

Implementasi dan Total Count

Untuk menghitung total item (misalnya untuk badge jumlah), jalankan query count seperti SELECT count(*) FROM posts terpisah atau simpan sebagai field di connection. Ingat bahwa menambahkan count bisa mahal pada tabel besar — pertimbangkan caching atau estimasi.

Pagination Best Practices

Default dan Batas Maksimum

  • Tentukan default page size kecil (10-25) agar response tetap ringan.
  • Tentukan maksimum limit (misalnya 100) dan tolak atau potong bila dilampaui.
  • Untuk navigasi halaman bernomor, gabungkan offset dengan argumen page dan pageSize.
JSBatas maksimum limit
const MAX_LIMIT = 100;
const limit = Math.min(args.first ?? 20, MAX_LIMIT);

Caching dan Loading States

Hasil paginated bersifat cacheable bila urutan datanya stabil. Di client, simpan endCursor dari response terakhir untuk request berikutnya, dan pertahankan item yang sudah dimuat saat memuat halaman baru. Ini menjadi dasar infinite scroll yang halus — detail sisi client-nya akan dibahas di episode 25.

Penutup

Inti yang harus dibawa pulang:

  • Semua field list wajib punya pagination di API production.
  • Offset pagination sederhana, tapi tidak stabil saat data berubah.
  • Cursor pagination memakai penanda stabil dan cocok untuk feed dinamis.
  • Relay Connection spec memberi struktur universal: edges, node, cursor, dan pageInfo.
  • Ambil first + 1 item untuk menentukan hasNextPage.
  • Terapkan default dan batas maksimum ukuran halaman.

Di episode 13 selanjutnya kalian akan mempelajari authentication dengan JWT — konsep authentication versus authorization, struktur JWT, implementasi mutation login dan signup dengan bcrypt, context-based authentication, pola refresh token, hingga integrasi OAuth Google dan GitHub. Keamanan akses ke API kalian akan dimulai!

Belajar GraphQL - Implementasi Pagination (Offset, Cursor, Relay) | Belajar GraphQL