Reputation: 1481
I've run into an issue while trying to extend my API to include a GraphQL endpoint. The application I'm working on is a kind of forum with Messages
. A message can contain comments of type Message
. If a message is a comment it has a parent of type Message
. Simplified, the schema looks like this:
type Message {
id: String
content: String
comments: [Message]
parent: Message
}
type RootQuery {
message(id: String): Message
messages: [Message]
}
The problem with this schema is that it allows for queries like this:
{
messages {
comments {
parent {
comments {
parent {
comments {
parent {
id
content
}
}
}
}
}
}
}
}
Keep in mind that I may want to allow for arbitrarily deep nesting of comments. In that case the following query should be allowed:
{
messages {
comments {
comments {
comments {
id
content
}
}
}
}
}
So, my question is this: Should I introduce a new type - Comment - to the API that do not know of its parent? Or are there any other ways of restricting this kind of unwanted behaviour?
Also, would the use of a Comment-type prohibit me from using the fragment messageFields on Message
syntax in my queries? Perhaps this is the time to introduce interfaces to the schema?
Suggestion to a solution if I introduce the type Comment (I have not tried this):
interface Message {
id: String
content: String
comments: [Message]
}
type DefaultMessage : Message {
id: String
content: String
comments: [Comment]
parent: Message
}
type Comment : Message {
id: String
content: String
comments: [Message]
}
type RootQuery {
message(id: String): Message
messages: [Message]
}
Upvotes: 7
Views: 3482
Reputation: 1309
Just in case anyone else ends up here wondering how to do recursive types in graphql-js, there's a useful hint in graphql-js's code:
* When two types need to refer to each other, or a type needs to refer to
* itself in a field, you can use a function expression (aka a closure or a
* thunk) to supply the fields lazily.
*
* Example:
*
* var PersonType = new GraphQLObjectType({
* name: 'Person',
* fields: () => ({
* name: { type: GraphQLString },
* bestFriend: { type: PersonType },
* })
* });
*
*/
https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L274
Upvotes: 4
Reputation: 1165
I suppose you have a depth
attribute of the Comment
data structure, which should be pretty useful, for example, to limit the max nested depth when the users are posting comments.
So that your problem could be solved like this: in the resolver
of the comments
attribute, check the depth
, return nothing if the depth
is going illegal, otherwise fetch the comments and return.
Upvotes: 0
Reputation: 8741
If a message is a comment it has a parent of type Message.
Looks like the parent
field should be under type Comment
, not DefaultMessage
. That still wouldn't prevent a parent - comments - parent
query but if you're worried about this for a DDOS reason, there are many other types of requests that are difficult to compute, even with REST APIs, and you should have other measures to detect such an attack.
However, you pose a very interesting question with the nested comments. How would you know how many times you need to nest the comment
in the query to get all nested responses? I don't think it's currently possible with GraphQL to specify recursive objects.
I'd probably go around this limitation by fetching each nested comment one by one (or by X levels at a time) starting from the last comment as the node
{
messages {
comments {
id
content
}
}
}
followed by
{
node(commendId) {
comment {
id
content
}
}
}
Upvotes: 0