How to return an "Edge" from Sangria-Relay

384 views
Skip to first unread message

Nelanka Perera

unread,
Feb 9, 2016, 4:50:40 PM2/9/16
to sangria-graphql
Thanks to you all! Any documentation and examples (besides Star Wars) ;) are so welcome in learning this stuff.

I'm working with Sangria and have a schema that seems to work. Now the client guys want an "Edge" returned instead of the Entity object I was sending back. I'm familiar with Edges and Connections as concepts, but have only used Connections to return some sequence of objects. My question is, does returning an edge from a query make sense, and if so, how would I do it? I looked at sangria.relay.Edge, but that seems to be just the start.


Thanks,
Nelanka

Oleg Ilyenko

unread,
Feb 9, 2016, 7:56:33 PM2/9/16
to sangria-graphql
Hi Nelanka,

Edge is generally has some meaning inside of a connection: it's a cursor + node (the entity inside of a collection). Outside of a connection cursor (and as a direct consequence of it - `Edge`) does not have much meaning. You mention that you already have used `Connection`. Was it a `sangria.relay.Connection` or have you defined `Connection` GraphQL type on your own?

Connection is generally a collection of nodes (entities like `User` or  `Product`). Relay wraps every node in `Edge` in order to provide a cursor for every node in a collection. This cursor information can be used to make consequent queries and fetch more elements of this collection that come after the node with this cursor. For instance you can make queries like this:

query FriendsQuery {
  user {
    friends(first: 2, after: "YXJyYXljb25uZWN0aW9uOjE=") {
      totalCount
      edges {
        # ...
      }
    }
  }
}

`"YXJyYXljb25uZWN0aW9uOjE="` in this case is a cursor. sangria-relay provides some helper functions to handle scala's `Seq` as a collection. They encode an index of a node inside of a collection in a cursor. But it's just one of possible implementations: cursor can be anything. 

sangria-relay provides you with a set of helper functions to define connections with edges. Here is one of the examples (sorry for referencing starwars example once again :) ):


Here is the code:

val ConnectionDefinition(edgeType, shipConnection) = Connection.definition[ShipRepo, Connection, Option[Ship]]("Ship", OptionType(ShipType))

This will define edge GraphQL type (which in this case would be called `ShipEdge`) and a connection type for a ship. As you will notice later, in this example we are operating on the whole ships list, since it's held in memory:

Field("ships", OptionType(shipConnection), arguments = Connection.Args.All,
  resolve = ctx ⇒ Connection.connectionFromSeq(ctx.value.ships map ctx.ctx.getShip, ConnectionArgs(ctx)))))

In this case we can use `connectionFromSeq` to provide pagination for the ships collection. `connectionFromSeq` will also transparently create edges with index-based cursors for you. If the full collection can't be held in memory (e.g. only one page of the collection comes from the database), then you can either use overloaded version of `connectionFromSeq` with `SliceInfo` argument, or you need to implement your own `Collection` and `Edge` types which better fit your scenario.

Hope this explanation helped a bit and haven't made the whole thing even more confusing :) It something is not clear yet or you have further questions, please let me know.

Cheers,
Oleg

Nelanka Perera

unread,
Feb 10, 2016, 10:12:44 AM2/10/16
to sangria-graphql
Thanks Oleg for you detailed response. To answer your question, I'm using sangria.relay.Connection. I've used it in the manner you describe with:

/**
* type PostConnection {
* edges: [PostEdge]
* pageInfo: PageInfo!
* }
*
* type PostEdge {
* cursor: String!
* node: Post
* }
*/
val ConnectionDefinition(_, postConnection) =
Connection.definition[MyContext, Connection, Post]("Post", PostObject)

Now I have a Post object too:

val PostObject: ObjectType[MyContext, Post] = ...

My query was previously returning this object:

* type CreatePostPayload {
* post: Post! // Instead return postEdge: PostEdge!
* clientMutationId: String!
* }

I was trying to understand how to return a PostEdge instead. What I realized was that we were throwing away the PostEdge type:

val ConnectionDefinition(postEdge, postConnection) =
  Connection.definition[MyContext, Connection, Post]("Post", PostObject)

Thanks for your help. It's getting clearer!

Cheers,
Nelanka

Nelanka Perera

unread,
Feb 10, 2016, 11:16:48 AM2/10/16
to sangria-graphql
Just to add to this. The purpose of returning a PostEdge was to work towards getting a "subscription" of posts as they're added. Does this usage make sense to achieve that end?

Nelanka

Oleg Ilyenko

unread,
Feb 10, 2016, 2:33:21 PM2/10/16
to sangria-graphql
Using edges to model a subscriptions is actually an interesting idea! The cursor can be something like event ID or post sequence ID. At the moment GraphQL (and sangria) has a special subscription type which is a top-level type, just like query or mutation. For instance you can make query like this:

subscription NewPosts {
  postsCreated
(
after: "YXJyYXljb25uZWN0aW9uOjE=") {
    if
    text
 
}
}

Th problem is that subscriptions semantics is underspecified in the spec at the moment. When you define a subscription type, it will work exactly like query type and will have the same semantics. It's only a temporary behavior: the semantics of subscriptions is actively discussed within GraphQL community at the moment.

On the other hand, the syntax for subscriptions is supported by libraries and tools, so it gives a lot of opportunity for experimentation with different concepts and approaches to subscriptions. I also have some ideas which I would like to play with before subscriptions semantics would be defined in the spec.

So in general I think it can be a viable approach to use edges in as a way to model the subscriptions. I would be definitely interested to hear back from you when you make some progress in this direction.
Reply all
Reply to author
Forward
0 new messages