Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are edges required in a Relay/GraphQL Connection?

In a Relay/GraphQL schema configuration, one-to-many relationships (with pagination) are specified as in the tutorial example

type ShipConnection {
  edges: [ShipEdge]
  pageInfo: PageInfo!
}
type ShipEdge {
  cursor: String!
  node: Ship
}

However, the one-to-one connection made by ShipEdge seems redundant. Why can't we move the cursor to ShipConnection and store an array of Ship IDs as edges?

type ShipConnection {
  edges: [Ship]
  pageInfo: PageInfo!
  cursor: String!
}

What were the design decisions to require one extra object for every edge in a one-to-many relationship?

like image 991
Michael Ho Chum Avatar asked Oct 24 '15 22:10

Michael Ho Chum


People also ask

What does edges mean in GraphQL?

The circles in the graph are called “nodes” and the lines of the graph are called “edges.” An edge is a line that connects two nodes together, representing some kind of relationship between the two nodes.

What are edges and nodes in GraphQL?

All of this is a lot of words (and excuses to post pictures of Space Shuttles) to say in a nutshell: the difference is that a node is a specific piece of data whereas edge allows you to access and use the relationship between that data and others as well. Use a node query if you just want the data.

What is Relay connection in GraphQL?

Connections are GraphQL fields that take a set of arguments to specify which "slice" of the list to query, and include in their response both the "slice" of the list that was requested, as well as information to indicate if there is more data available in the list and how to query it; this additional information can be ...

Should I use Relay for GraphQL?

Relay is recommended to use in the frontend to have more structured, modular, future-proofed applications that can scale easily to millions of users. The first thing that needs to be implemented in order to use Relay is to make a Relay-compatible GraphQL server.


2 Answers

(Updated with more explanations)

There are 3 ways to represent an array of data in GraphQL:

  1. List: Use when you have a finite list of associated objects that you're fine fetching all at once. In GraphQL SDL, this is represented as [Ship].
  2. Nodes: Use when you need to paginate over a list, usually because there can be thousands of items. Note that this is not part of the Relay specification and as such is not supported by the Relay client (instead, you'd wrap the item in an edge as described in #3), but some other clients such as Apollo are more flexible and support this construct (but you need to provide more boilerplate). In GraphQL, this would be represented as type ShipConnection { nodes: [Ship], pageInfo: PageInfo! }.
  3. Edges: Use when, in addition to pagination, you also need to provide extra information for each edge in the connection (read below for more details). In GraphQL, you'd write it as type ShipConnection { edges: [ShipEdge], pageInfo: PageInfo! }.

Note that your GraphQL server might support all three options for a specific association, and the client then selects which field they want. Here's how they'd all look together:

type Query {
  ships: [Ship]       // #1
  shipsConnection: [ShipConnection]
}

type ShipConnection {
  nodes: [Ship]       // #2
  edges: [ShipEdge]   // #3
  pageInfo: PageInfo!
}

type PageInfo {
  endCursor           // page-based pagination
  hasNextPage
}

type ShipEdge {
  cursor: String!     // edge-based pagination
  node: Ship
  // ... edge attributes
}

type Ship {
  // ... ship attributes
}

Lists (#1) should only ever be used when you know that the number of items won't grow (for example, if you have a Post, you may want to return tags as a List, but you shouldn't do that with comments). To decide between #2 and #3, there are two reasons for using edges over just plain nodes:

  • It's a place for edge-specific attributes. For example, if you have a User that belongs to many Groups, in a relational database you'd have a UserGroup table with user_id and group_id. This table can have additional attributes like role, joined_at etc. The GroupUserEdge would then be the place where you could access these attributes.

  • Have a place for the cursor. Relay, in addition to page-based pagination (using pageInfo) supports edge-based pagination. Why does Relay need a cursor for each edge? Because Relay intelligently merges data requirements from your entire app, it may already have a connection with the same parameters you're requesting but not enough records in it. To fetch the missing data, it can ask for data in the connection after some edge's cursor.

    I understand it may be confusing, considering databases have cursors, too, and there is only one cursor per query. A Relay connection is not a query really, it's rather a set of parameters that identify a query. A cursor of connection's edge is a set of parameters that identify a position within a connection. This is a higher abstraction level than a pure query cursor (remember that edges need to be able to identify a position even over a connection that might not be a DB query, or be hidden by a 3rd party system). Because of this required flexibility, one cursor for a connection would not be enough.

like image 76
Petr Bela Avatar answered Oct 14 '22 01:10

Petr Bela


We've written a blog article about the differences between a simple GraphQL schema vs a Relay-specific schema:

https://www.prisma.io/blog/connections-edges-nodes-in-relay-758d358aa4c7

like image 42
schickling Avatar answered Oct 13 '22 23:10

schickling