question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Client 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:closed
  • Created 4 years ago
  • Comments:47 (38 by maintainers)

github_iconTop GitHub Comments

2reactions
tunurgitrcommented, Aug 21, 2019

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.

{
  # will lead to a compile error 
  foo @spread {
     bar 
  }
  bar {
     foo
  }
}
2reactions
michaelstaibcommented, Aug 21, 2019

@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.

  1. If the list is a scalar list and we put @spread on it, we will throw an exception.

    {
      foo {
         # will lead to a compile error 
         scalarList @spread
      }
    }
    
  2. If the field is a scalar it cannot be spread and will throw an exception

    {
      foo {
         # will lead to a compile error 
         bar @spread
      }
    }
    
  3. If we have collisions we will throw an error

    {
      # will lead to a compile error 
      foo @spread {
         bar 
      }
      bar {
         foo
      }
    }
    

OK, now the cases that will work:

  1. A simple object can be spread when.

    {
      foo @spread {
         bar
         baz
      }
      qux {
        quux
      }
    }
    
  2. 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.

    {
      users  @spread {
         username
      }
    }
    

    would become in c#

    public IReadOnlyList<string> Username { get; }
    

    In order to get a nice property name Usernames you can use an alias in the query.

Read more comments on GitHub >

github_iconTop Results From Across the Web

What is Client (in API Terms)?
An API client is a set of tools and protocols that operate from an application on a computer. They help you to bypass...
Read more >
What Is an API Client? | Definition & Use Cases
An API client is a development tool that makes it easier for producers and consumers to explore, test, and debug APIs. Traditional approaches...
Read more >
Client API Reference for model-driven apps - Power Apps
This section contains reference documentation for client API object model that can be used with JavaScript libraries. Important. The Client API ...
Read more >
Client-side web APIs - Learn web development | MDN
APIs are programming features for manipulating different aspects of the browser and operating system the site is running on, or manipulating ...
Read more >
difference between client API and server API [closed]
Server-Side means it will be executed from another machine, a remote machine, a server. In fact, when we say something is server-side ,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found