Relay-Style Connections and Pagination FAQ
Common questions around Relay's Connection specification
Relay is an opinionated GraphQL client, and its associated Connections specification defines a pattern for expressing one-to-many relationships in a GraphQL schema:
query MyPosts($cursor: String) {viewer {posts(first: 5, after: $cursor) {edges {node {idtitlecontent}cursor}pageInfo {hasNextPageendCursor}}}}
It's worth noting that Facebook designed the Connections specification for their Newsfeed feature with these features in mind:
- It uses cursor-based pagination.
- It supports paging backward (with the
before
cursor) and forward (with theafter
cursor). - Each item in the list has a cursor you can use to jump to a specific page in the middle of the list.
These features might not perfectly meet your requirements or the capabilities of your downstream data sources.
Do I have to use Relay-style connections?
No, unless you're using the Relay client. But its popularity outside of the Relay ecosystem is worth taking advantage of:
- Many developers are familiar with the connection pattern.
- It encapsulates several schema design best practices.
- It's designed to be future-proof and support gradual evolution of your GraphQL schema.
Do I even need a wrapper type for my lists?
Consider the "Zero, One, Infinity" rule—can you definitively assert that your list will never require pagination or other metadata about the relationship?
Using a wrapper type for lists provides the following benefits:
Avoid breaking changes: You can initially return a wrapper type that doesn't use pagination, and then add pagination later without breaking existing clients. If you return a list directly, you can't add pagination metadata later.
Represent entity relationships: The
Connection
andEdge
wrapper types support fields that model attributes of the relationship between entities that don't belong in the entities themselves.Consider this example of a many-to-many relationship between
Business
andCustomer
:type Business {id: IDcustomers: CustomerConnection}type CustomerConnection {edges: [CustomerEdge]total: Int}type CustomerEdge {node: Customertype: CustomerType}enum CustomerType {IN_STOREONLINEMULTI_CHANNEL}type Customer {id: IDshopsAt: BusinessConnection # --snip --}A specific
Customer
might shop at one businessIN_STORE
and anotherONLINE
. Thetype
is an attribute of the relationship, not the business or customer itself. Without wrapper types, you don't have a place to put this data.
Do I have to implement the entire connection specification?
No, you can use a subset of the specification. You can implement additional parts over time to reach full compliance with the specification (if necessary).
If your downstream data sources don't support paging backward, you limit your implementation to forward pagination:
If your downstream data sources don't support per-node cursors, you can drop the edges
field and use nodes
:
Are there other ways to design my schema for pagination?
Yes. If your requirements or downstream capabilities don't fit the Relay-style connections spec, we recommend using a visibly different set of conventions so that it's clear to graph consumers that they shouldn't expect to use Relay connection patterns.
Here's an example of pagination that uses page offsets and supports a UI for jumping to a specific page:
Can I use Relay-style connections with Apollo Federation?
Yes! You define the schema and resolvers for the connection relationship within a single subgraph, so federation has almost no ramifications on the pattern.
The one exception is the PageInfo
type, which commonly has a consistent definition for all connections. You must mark this type's definition as @shareable
to define it in multiple subgraphs:
type PageInfo @shareable {hasNextPage: Boolean!hasPreviousPage: Boolean!startCursor: StringendCursor: String}