I am trying to understand more complex graphql apis that implement the Relay Cursor Connections Specification
If you look at the query below that I run on the github graphql api explorer
{
repository(owner: "getsmarter", name: "moodle-api") {
id
issues(first:2 ) {
edges {
node {
id
body
}
}
nodes {
body
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
totalCount
}
}
}
Notice it has the fields edges and nodes.
Why does github have an additional field called nodes in their api? Why don’t they just use the edges field since you can get the same data from edges? Is this just for convenience?
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.
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.
A GraphQL schema is a description of the data clients can request from a GraphQL API. It also defines the queries and mutation functions that the client can use to read and write data from the GraphQL server. In other words, you specify your client or application UI data requirements in your GraphQL schema.
In the GraphQL specification, the node query is a query that takes only an ID and returns the object corresponding to that ID. In a REST API, this is similar to a GET request that fetches an object by ID, but with an important difference: the node query does not require the type of that object to be specified.
If we look at the general structure of the common connection implementation you typically have the following:
TypeA -> TypeAToTypeBConnection (usually a field on TypeA with a name like typeBConnection
) -> TypeAToTypeBEdge (usually field of name on connection with name edges
) -> TypeB (usually field name on an edge with name node
)
A -> connection -> edges -> B
Connection types will normally have fields containing information which is specific to the entire connection which is typically paging information, total counts, etc.
Edge types normally have fields which have information which is specific to that connection but not common to all nodes. The most common field in this case is cursor
which represents the nodes ‘location’ in the connection which is not a globally unique ID but a way to return to that location in the connection.
Node type is normally just the type which the connection goes too which contains no connection specific information
In the case of github’s API the Edge type has the commonly implemented cursor
field which can be used as a reference within that connection later. They also have a field which bypasses the edge
type in the case you don't need the cursors. This is why you see both edges
and nodes
fields directly off the connection type.
To see these cursor fields you can send the following query to see what I am talking about:
{
repository(owner: "getsmarter", name: "moodle-api") {
issues(first:2 ) {
edges {
cursor
node {
id
}
}
}
}
}
For more detail on this style of connection take a look here: https://facebook.github.io/relay/graphql/connections.htm
EDIT - Additional response: The purpose of allowing access to both an edge type and a node type right at the connection could be for, at least, 2 reasons which I can think of. Firstly, for the convenience of those using the API when their use case does not require cursors. Second, there might be a case in which, depending on the query sent, they may not need to ever even generate cursors. The second would likely be minimal savings in CPU time and would probably be more trouble than it is worth.
Having implemented cursors in a GraphQL endpoint myself in the past, once you get over the how, the actual generation of them is not really all that difficult. It is simply a matter of serializing a few key pieces of information. It also might be worth noting, it is pretty trivial to provide both (A->conn->edge->B
and A->conn->B
) once you have already created the Edge type.
As I do not work for Github, I can’t tell you what exact intention was. However, I would most definitely think it is the first reason… simply developer convenience.
A node is always the same, regardless of how you get to it. The edge is metadata about that node in the context of the connection, usually just the cursor, but you could also add things like a relevancy score if your connection represented a search query. This data shouldn't exist on the node itself, because it makes no sense in a different context.
Terminology:
It's likely just a convenience for them since they probably have some crazy queries and it reduces object lookup in JavaScript. Edges will also contain a cursor
property as well as a node
property, which they likely don't need everywhere, and therefore another benefit of having a top-level node
field.
I should also note that the edge/cursor convention is strongly geared to a Relay specific environment, and furthermore a cursor-based paging system where you can only move by a single index/page. If your desire is to create a more legacy-based paging system, then you don't have to implement this type of paging.
A use-case that breaks cursor-paging is if clients want to jump to page 5, and are on page 1, which isn't possible in Relay since cursors are opaque, and are the basis for "where" in a collection you're currently at.
Hope that helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With