Graphql Connections
title: "GraphQL Connections — Relay Pagination for GraphQL APIs" description: "GraphQL Relay connections provide cursor-based pagination with edges, nodes, and pageInfo, offering a standardized approach for paginating GraphQL queries." date: 2026-06-28 lastmod: 2026-06-28 weight: 22 tags: [apis, pagination] }
GraphQL Relay connections implement cursor-based pagination using edges (with cursors) and nodes (the actual data), providing stable, reusable pagination.
What You'll Learn
- Relay connection specification
- Edges, nodes, and pageInfo
- forward/backward pagination arguments
Why It Matters
Relay connections are the standard pagination pattern for GraphQL APIs. Any GraphQL client expecting Relay pagination can consume your API.
Connection Structure
type Query {
users(first: Int, after: String, last: Int, before: String): UserConnection
}
type UserConnection {
edges: [UserEdge]
pageInfo: PageInfo!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Code Examples
# GraphQL Relay connection resolver
import graphene
from graphene import relay
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
class UserConnection(relay.Connection):
class Meta:
node = User
class Query(graphene.ObjectType):
users = relay.ConnectionField(UserConnection)
def resolve_users(self, info, first=None, after=None, **kwargs):
query = User.get_query(info)
return relay.ConnectionFromIterable.resolve_connection(
UserConnection, query, args={"first": first, "after": after}
)
# Server-side resolver with cursor logic
def resolve_users(self, info, first=None, after=None):
limit = first or 20
cursor = decode_cursor(after) if after else None
if cursor:
users = db.execute(
"SELECT * FROM users WHERE id > %s ORDER BY id LIMIT %s",
[cursor, limit + 1]
)
else:
users = db.execute(
"SELECT * FROM users ORDER BY id LIMIT %s",
[limit + 1]
)
has_next = len(users) > limit
if has_next:
users = users[:limit]
edges = [
{"node": u, "cursor": encode_cursor(u.id)}
for u in users
]
return {
"edges": edges,
"pageInfo": {
"hasNextPage": has_next,
"hasPreviousPage": False,
"startCursor": edges[0]["cursor"] if edges else None,
"endCursor": edges[-1]["cursor"] if edges else None
}
}
Common Mistakes
1. Not Following Relay Spec
Use first/after for forward and last/before for backward pagination.
2. Missing pageInfo
hasNextPage and hasPreviousPage are required in the connection spec.
3. Cursors Not Opaque
Cursors should be base64-encoded strings, not raw database IDs.
4. No Total Count
Relay connections don't require total count, but you can add it.
5. Incorrect hasNextPage Logic
Fetch limit + 1 items. If you get more than limit, there's a next page.
Practice Questions
- What are the three main parts of a Relay connection?
- What arguments control forward pagination?
- What arguments control backward pagination?
- Why are cursors opaque to clients?
- How do you determine hasNextPage?
Answers:
- edges, nodes (in edges), and pageInfo.
first(count) andafter(cursor).last(count) andbefore(cursor).- So clients don't depend on cursor format, allowing server-side changes.
- Fetch n+1 items; if you get n+1, hasNextPage = true.
Challenge: Implement a GraphQL Relay connection for a blog with posts and comments. Support both forward and backward pagination with proper pageInfo.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro