Client API
See original GitHub issueClient API
We have experimented quite a lot with a GraphQL client API. We looked at things like the GitHub OctoKit implementation and where highly inspired by it. In the end we decided to not pursue this idea any more since with it we are trying to emulate GraphQL with .Net which leads to awkward syntax constructions. With such an API the user would not only have to learn GraphQL but also our LINQ syntax and then try to figure out which LINQ construct would lead to a desired GraphQL syntax.
We also had a look at how JavaScript and in particular Relay does things. With Relay the fragment is front and center and describes the data that you want to consume as an object for your component.
The nice thing about JavaScript is that you have template literals. This is something we do not have in C#. It would be actually awesome to have something like this, since we could reinvent how you consume data in frameworks like Xamarin or Blazor.
So, the main thing here is that we have come to the following conclusions about the client API:
- We want to use GraphQL to write GraphQL.
- We want that everything compiles (including the GraphQL).
- We want strong types that generate from the queries.
Using GraphQL to write GraphQL
The basic idea would be to use GraphQL files ending on the extension .graphql
, instead of magic strings. Lets say we have the following project structure.
root
|-- Programm.cs
|-- Demo.csproj
We would create a new folder that shall contain our GraphQL client, in this folder we will add a small configuration like the following:
{
"servers": [
{ "MyCustomSchemaName": "http://localhost:5000" }
]
}
The config will hold all the GraphQL servers that we are dealing with, if there are more than one, the client will auto-stitch them together.
Next we will add the Hot Chocolate client package to our project.
The client will bring in local tooling as well as the core APIs. The local tooling will let me update the schema and on build we will compile the query files against the schema. Syntax errors would result in build errors.
We would initialize the schemas by doing something like:
dotnet graphql init
By then our folder structure would look like the following:
root
|-- Programm.cs
|-- Demo.csproj
|-- OurClient
|-- config.json
|-- MyCustomSchemaName.graphql
The next thing to do would be to add query document which is another GraphQL file. We will use fragments to control the type name structure.
query GetUser($userId: ID!) {
user(id:$userId) {
... User
}
}
fragment User on User {
name
}
When we compile the msbuild integration of our package will turn all the GraphQL query documents into C# code.
This can also be done by triggering our .Net tool manually:
dotnet graphql compile
root
|-- Programm.cs
|-- Demo.csproj
|-- OurClient
|-- config.json
|-- MyCustomSchemaName.graphql
|-- Generated
|-- User.cs
|-- IClient.cs
|-- ...
In order to not be completely confined by fragments we will add a couple of directives that can help you to name things.
query GetUser($userId: ID!) {
user(id:$userId) @type(name: "User") {
name
}
}
fragment User on User {
name
}
The above would yield the same client like before.
Everything Compiles
The compiler integration will ensure that not only the GraphQL will yield a generated client but also that the GraphQL is correct and validated against the schema. Syntax errors will occur in IDEs in the error list and can be clicked on to get to the exact position in the GraphQL document that caused the error.
Generate from Query Documents
We will generate the clients from the query documents and not from the schema. We will still use the schema to generate but we will not generate all types for the schema. The query document define what types will be generated and how these types will be generated.
Transport
We will abstract the transport into a separate package that will bring in support for subscriptions and batching and other transport features like @defer
, @stream
and paging or even persisted queries.
Persisted Queries
We have to ensure that the compiled queries contain the hashes so that we can use persisted query features.
Stitching
The client will handle auto-stitching of schemas this will allow users to write query documents that will fetch from multiple sources in one go with one client.
## Local Resolvers
We will allow users to add schema extension files that extend on the server schemas.
extend type User {
someExtraData: String! @localField
someStitchedData_ @delegate(schema: "Someschema" path: "...")
}
Also with the extensions we will support the stitching directives.
We still have to explore how to define the local resolvers.
Issue Analytics
- State:
- Created 4 years ago
- Comments:47 (38 by maintainers)
Top GitHub Comments
fwiw, for collision errors, I’d hope for something like:
Unable to spread "Foo" selections onto "Query". Field "bar" is selected on both types. Alias one of those fields to continue.
@tunurgitr @rstaib
I did a bit more implementation yesterday night and I think we can also spread lists.
Let me put out some things that will lead to errors first.
If the list is a scalar list and we put
@spread
on it, we will throw an exception.If the field is a scalar it cannot be spread and will throw an exception
If we have collisions we will throw an error
OK, now the cases that will work:
A simple object can be spread when.
If the field that is spread is a list of object then it can be spread and the spread fields become lists. This can be very useful if you for instance fetch all the usernames but really do not want a user object with just a name but rather really a list of usernames.
would become in c#
In order to get a nice property name
Usernames
you can use an alias in the query.