Join us from October 8-10 in New York City to learn the latest tips, trends, and news about GraphQL federation and API platform engineering.Join us for GraphQL Summit 2024 in NYC
Docs
Start for Free

Relay-Style Connections and Pagination FAQ

Common questions around Relay's Connection specification

best-practicesschema-design

Relay is an opinionated , and its associated Connections specification defines a pattern for expressing one-to-many relationships in a :

query MyPosts($cursor: String) {
viewer {
posts(first: 5, after: $cursor) {
edges {
node {
id
title
content
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
}

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 the after 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 .

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 relationships: The Connection and Edge wrapper types support 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 and Customer:

    type Business {
    id: ID
    customers: CustomerConnection
    }
    type CustomerConnection {
    edges: [CustomerEdge]
    total: Int
    }
    type CustomerEdge {
    node: Customer
    type: CustomerType
    }
    enum CustomerType {
    IN_STORE
    ONLINE
    MULTI_CHANNEL
    }
    type Customer {
    id: ID
    shopsAt: BusinessConnection # --snip --
    }

    A specific Customer might shop at one business IN_STORE and another ONLINE. The type 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 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 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 for the connection relationship within a single , 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 :

type PageInfo @shareable {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Next
Home
Rate articleRateEdit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company