Pagination di GraphQL
List data butuh pagination. Dua pendekatan: offset-based (sederhana) dan cursor-based (scalable).
Offset-based
query {
users(limit: 10, offset: 20) {
id
name
}
}
# Resolver
users: (_, { limit, offset }) => {
return db.users.findMany({ take: limit, skip: offset });
}
# Masalah: jika data berubah antara page loads,
# items bisa terlewat atau muncul double
Cursor-based (Relay Spec)
# Schema
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String! # opaque cursor (encoded ID/timestamp)
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Query
query {
users(first: 10, after: "cursor_abc") {
edges {
node {
id
name
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
Cursor Implementation
// Cursor = base64 encoded ID
const toCursor = (id) => Buffer.from(`cursor:${id}`).toString("base64");
const fromCursor = (cursor) => Buffer.from(cursor, "base64").toString().split(":")[1];
// Resolver
users: async (_, { first, after }) => {
const afterId = after ? fromCursor(after) : null;
const users = await db.users.findMany({
take: first + 1, // fetch one extra to check hasNextPage
...(afterId && { cursor: { id: afterId }, skip: 1 }),
orderBy: { id: "asc" },
});
const hasNextPage = users.length > first;
const edges = users.slice(0, first).map(u => ({
node: u,
cursor: toCursor(u.id),
}));
return {
edges,
pageInfo: {
hasNextPage,
endCursor: edges[edges.length - 1]?.cursor,
},
};
}