ZeroQL V3 - C# friendly GraphQL client
Tags: csharp, graphql, webdev, opensource
Published: 2023-02-28 15:45:48

Hello there! I want to share the new features of ZeroQL's latest release. This release contains many improvements, including support for unions and interfaces, automatic scalars generation, and config file support.

## Introducing ZeroQL's Config File

With the latest release, ZeroQL introduces support for config files. This means that you no longer have to memorize CLI options or type long commands every time you want to generate a GraphQL client. You can now create a configuration file with all your desired options.

Let's suppose we want to execute the next command:
``` bash
dotnet zeroql generate --schema .\schema.graphql --namespace TestServer.Client --client-name TestServerGraphQLClient --output Generated/GraphQL.g.cs
```

We can use ` dotnet zeroql config init `. It will create the ` zeroql.json ` file with the following content:
``` json 
{
  "$schema": "https://raw.githubusercontent.com/byme8/ZeroQL/main/schema.verified.json",
  "graphql": "./schema.graphql",
  "namespace": "TestServer.Client",
  "clientName": "TestServerGraphQLClient",
  "output": "./Generated/GraphQL.g.cs"
}
```
Now we can use CLI like that:
``` bash
dotnet zeroql -c ./zeroql.json
```

## Unions and interfaces

ZeroQL's latest update brings proper support for unions and interfaces. This means that you can now work with these GraphQL types in your C# code using an intuitive and straightforward syntax.

To demonstrate this feature, let's take a look at an example GraphQL schema containing interfaces:
``` graphql
schema {
  query: Query
}

interface IFigure {
  perimeter: Float!
}

type Circle implements IFigure {
  center: Point!
  radius: Float!
  perimeter: Float!
}

type Point implements IFigure {
  x: Float!
  y: Float!
  perimeter: Float!
}

type Square implements IFigure {
  topLeft: Point!
  bottomRight: Point!
  perimeter: Float!
}

type Query {
  figures: [IFigure!]!
}
```

Now we can get figures by writing the next C# query:
``` csharp
var response = await qlClient.Query(static q => q
    .Figures(f => new
    {
        f.Perimeter,
        Circle = f.On<Circle>().Select(c => new
        {
            c.Radius,
            Center = c.Center(p => new { p.X, p.Y })
        }),
        Square = f.On<Square>().Select(s => new
        {
            TopLeft = s.TopLeft(p => new { p.X, p.Y }),
            BottomRight = s.BottomRight(p => new { p.X, p.Y })
        })
    }));

Console.WriteLine(JsonSerializer.Serialize(response)); 
// {
//   "Query": "query { figures { perimeter ... on Circle { radius center { x y }  } ... on Square { topLeft { x y }  topLeft { x y }  } __typename } }",
//   "Data": [
//     {
//       "Perimeter": 6.2831854820251465,
//       "Circle": {
//         "Radius": 1,
//         "Center": {
//           "X": 1,
//           "Y": 1
//         }
//       }
//     },
//     {
//       "Perimeter": 40,
//       "Square": {
//         "TopLeft": {
//           "X": 1,
//           "Y": 1
//         },
//         "BottomRight": {
//           "X": 11,
//           "Y": 11
//         }
//       }
//     }
//   ]
// }
```
Basically, every interface is treated as a C# interface. Then we can use the extension method ` On ` to select the concrete implementation we want.

The same logic applies to unions too. Here is the GraphQL schema example:
``` graphql
schema {
  query: Query
}

type TextContent {
  text: String!
}

type ImageContent {
  imageUrl: String!
  height: Int!
}

union PostContent = TextContent | ImageContent

type Query {
  posts: [PostContent!]!
}
```

And here is how we can write a query to get a result:
``` csharp
var response = await qlClient.Query(static q => q
        .Posts(
            o => new
            {
                Image = o.On<ImageContent>().Select(oo => new
                {
                    oo.ImageUrl,
                    oo.Height
                }),
                Text = o.On<TextContent>().Select(oo => new
                {
                    oo.Text
                }),
            }));

Console.WriteLine(JsonSerializer.Serialize(response));
// {
//   "Query": "query { posts { ... on ImageContent { imageUrl height } ... on TextContent { text } __typename } }",
//   "Data": [
//     {
//       "Image": {
//         "ImageUrl": "http://example.com/image.png",
//         "Height": 1920
//       }
//     },
//     {
//       "Text": {
//         "Text": "Hello World!"
//       }
//     }
//   ]
// }
```
As you can see on the C# level, there is no difference between the interface and the union types. All of them have identical syntax and behavior.

## Automatic Scalar Generation

ZeroQL now supports automatic scalar generation, which means that you no longer have to define custom types and JSON serializers for every scalar type you use in your GraphQL schema. This feature automatically generates C# types and JSON serializers for every scalar type used in your schema, making it easier and faster to work with GraphQL.

To use this feature, you simply define your scalar types in your GraphQL schema like this:
``` graphql
schema {
  query: Query
}

scalar PostId;

type Post {
  id: PostId,
  text: String!
  imageUrl: String!
  height: Int!
}

type Query {
  myPosts: [Post!]!
}
```

When you generate your client using ZeroQL, it will automatically generate a C# record for the ` PostId` scalar type, along with a JSON serializer. This makes it easy to work with scalars in your C# code without manually defining custom types and serializers. In this case, it would look like that:
``` csharp
public sealed record PostId : ZeroQLScalar
{
    public PostId()
    {
    }

    public PostId(string value)
    {
        Value = value;
    }

    public static implicit operator PostId(string value) => new PostId(value);
    public static implicit operator string (PostId scalar) => scalar.Value;
}
```
As a result, you can have as many scalars as you want, and all of them will have a proper representation on the C# level automatically.

## Conclusion

It is a solid update for ZeroQL. It extends support for GraphQL capabilities, simplifies configuration management, and, at the same time, maintains the outstanding performance of a raw HTTP request.

If you have any questions or want to find more detailed instructions on how to use the library, you can find them on [Github](https://github.com/byme8/ZeroQL).