Pagination¶
Viaduct implements the Relay Connection specification, providing cursor-based pagination with built-in builder utilities.
Schema¶
Define your connection and edge types using the @connection and @edge directives:
type FilmConnection @connection {
edges: [FilmEdge!]!
pageInfo: PageInfo!
}
type FilmEdge @edge {
node: Film!
cursor: String!
}
Fields that return a connection accept standard Relay pagination arguments:
type Character implements Node {
id: ID!
name: String!
films(first: Int, after: String): FilmConnection!
}
Building Connection Responses¶
Connection builders provide three strategies depending on your backend:
fromList — in-memory list¶
Use when your resolver has the full dataset. Viaduct handles slicing and cursor encoding automatically:
@Resolver
class CharacterFilmsResolver : CharacterResolvers.Films() {
override suspend fun resolve(ctx: Context): FilmConnection {
val allFilms = filmRepository.getFilmsForCharacter(ctx.objectValue.id)
return FilmConnection.Builder(ctx)
.fromList(allFilms) { film -> Film.Builder(ctx).title(film.title).build() }
.build()
}
}
fromSlice — offset/limit backend¶
Use when your backend accepts offset and limit. Over-fetch by one to detect hasNextPage:
@Resolver
class CharacterFilmsResolver : CharacterResolvers.Films() {
override suspend fun resolve(ctx: Context): FilmConnection {
val (offset, limit) = ctx.arguments.toOffsetLimit()
val fetched = filmService.getFilms(offset, limit + 1)
return FilmConnection.Builder(ctx)
.fromSlice(fetched.take(limit), hasNextPage = fetched.size > limit) { film ->
Film.Builder(ctx).title(film.title).build()
}
.build()
}
}
fromEdges — native cursors or edge metadata¶
Use when your backend returns native cursors, or when your edge type carries extra fields beyond node and cursor:
@Resolver
class CharacterFilmsResolver : CharacterResolvers.Films() {
override suspend fun resolve(ctx: Context): FilmConnection {
val response = filmService.getFilms(cursor = ctx.arguments.after, limit = ctx.arguments.first ?: 20)
return FilmConnection.Builder(ctx)
.fromEdges(
edges = response.films.map { film ->
FilmEdge.Builder(ctx)
.node(Film.Builder(ctx).title(film.title).build())
.cursor(film.cursor)
.build()
},
hasNextPage = response.hasMore
)
.build()
}
}
Full Documentation¶
For the complete pagination reference — argument types, toOffsetLimit(), cursor encoding, and PageInfo handling — see the Pagination developer docs.