I have played around with the current binding API, and it has a few pros and cons. There's a couple of possible directions, that I'd like to outline.
GraphQL server 'as a database'
The Graphcool GraphQL server closely mimics a database. This might validate an API that's closer to traditional ORMbinding libraries. These libraries are more centered around a Type, instead of a query/mutation, and would look something like this (inspired by libraries like Mongoose, Linq, etc.):
schema.Post.find({ name: 'abc' }) //or: schema.Post.findOne(...) and schema.Post.findMany(...)
new schema.Post({ name: 'abc' })
This type of API can only be achieved when the mapping between types and their corresponding query and mutation root fields is known. For Graphcool, they are, for generic GraphQL servers, they are not. This could be implemented using directives in the schema, for example:
type Query {
myPosts: [Post] @findMany
}
Generic GraphQL server
As seen above, the 'database like approach' required either convention or configuration to work, and would only work well for GraphQL schemas that actually mimic a traditional database design. The alternative is to stick to the root query and mutation fields, like the current implementation:
schema.allPosts(...) // or: schema.topPosts(...) or schema.posts(...)
This stays closest to the original schema you are binding to. I think this is a better fit for GraphQL binding.
Filtering and other parameters
Queries can have parameters. Most commonly used for filtering, ordering, aggregating etc. Currently, they are passed as arguments to the binding method. It might be worth investigating if field parameters can also become part of the API itself. So:
schema.posts({filter: { name: { starts_with: 'Hello' } } })
Could also be written as:
schema.posts.filter({ name: { starts_with: 'Hello' } })
Or even (when also generating types for GraphQL types):
schema.posts.filter(Post.name.startsWith('Hello'))
These calls could be chained too:
schema.posts.filter(...).orderBy(...)
This needs a lot more investigating to see if it can easily be determined which functions to generate. Maybe based on a check for scalar fields, to prevent generating fields for something like:
schema.post({ id: '123'})
schema.post.id('123')
Chaining
There was already an example of chaining in the previous chapter, where filter()
and orderBy()
were combined. But chaining also offers a possibility to implement functionality that is not offered by the server. For example, if the server doesn't offer aggregation options, this might be implemented as a function in the binding library:
schema.posts().aggregate('{ age }').avg('{ weight }').groupBy('{ age }')
This would be equivalent to SELECT age, avg(weight) FROM posts GROUP BY age
.
I believe the possibility to create your own binding, and add functionality like this in an easy way at the binding level, instead of the consuming resolver, would make it a lot easier to implement certain features.