Connections (Pagination)
View SourceThis guide covers Relay-style cursor-based pagination using connections.
Overview
Connections provide a standardized way to paginate lists in GraphQL, following the Relay Connection specification.
A connection includes:
edges- List of edge objects, each containing anodeandcursorpageInfo- Pagination metadata (hasNextPage, hasPreviousPage, cursors)
Defining a Connection
Use the connection macro inside a type:
defmodule MyApp.GraphQL.Types.User do
use GreenFairy.Type
alias MyApp.GraphQL.Types
type "User", struct: MyApp.User do
field :id, non_null(:id)
field :name, :string
# Paginated list of friends
connection :friends, Types.User do
# Custom edge fields (optional)
edge do
field :friendship_date, :datetime
field :friendship_status, :string
end
# Custom connection fields (optional)
field :total_count, :integer
end
end
endThis generates:
:friends_connectiontype withedges,pageInfo, and custom fields:friends_edgetype withnode,cursor, and custom edge fields:friendsfield with standard pagination arguments
Connection Arguments
Connections automatically receive these arguments:
first: Int- Return the first N itemsafter: String- Return items after this cursorlast: Int- Return the last N itemsbefore: String- Return items before this cursor
Resolving Connections
From a List
Use GreenFairy.Field.Connection.from_list/3:
field :friends, :friends_connection do
arg :first, :integer
arg :after, :string
arg :last, :integer
arg :before, :string
resolve fn user, args, _ ->
friends = MyApp.Accounts.list_friends(user)
GreenFairy.Field.Connection.from_list(friends, args)
end
endFrom an Ecto Query
Use GreenFairy.Field.Connection.from_query/4:
field :friends, :friends_connection do
resolve fn user, args, _ ->
query = MyApp.Accounts.friends_query(user)
GreenFairy.Field.Connection.from_query(query, MyApp.Repo, args)
end
endCustom Cursor
By default, cursors are Base64-encoded indices. You can provide a custom cursor function:
GreenFairy.Field.Connection.from_list(items, args,
cursor_fn: fn item, _index -> Base.encode64("item:#{item.id}") end
)PageInfo
The PageInfo type is automatically available and includes:
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}Example Query
{
user(id: "1") {
friends(first: 10, after: "Y3Vyc29yOjU=") {
edges {
cursor
friendshipDate
node {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
}Query Connections
You can also define connections at the query level:
defmodule MyApp.GraphQL.Queries.UserQueries do
use GreenFairy.Query
alias MyApp.GraphQL.Types
alias MyApp.GraphQL.Inputs
queries do
connection :users, Types.User do
arg :filter, Inputs.UserFilter
resolve fn _, args, _ ->
users = MyApp.Accounts.list_users(args[:filter])
GreenFairy.Field.Connection.from_list(users, args)
end
end
end
endConnection Enhancements
All connections include these additional fields:
nodes: [T!]- Direct access to nodes without edges (GitHub-style)totalCount: Int!- Total matching items (ignoring pagination)exists: Boolean!- Whether any items match the query
query {
users(first: 10) {
nodes { id name } # Simpler than edges
totalCount # 1523 total users
exists # true if any match
pageInfo { hasNextPage endCursor }
}
}totalCount and exists use deferred loading - the COUNT query only runs if these fields are requested.
Next Steps
- CQL - Add filtering and sorting to connections
- Relay - Full Relay specification support
- Relationships - DataLoader for efficient loading
- Operations - Query, mutation, and subscription modules
- Expose - Auto-generate query fields from types