Smarter APIs with GraphQL

Programming GraphQL .NET REST C#

GraphQL is a different way of thinking about how clients and servers communicate

RESTful APIs are the backbone of modern software. Whether you’re building a web app, a mobile app, or even a smart fridge, chances are you’re dealing with an API.

For years, we’ve relied on RESTful APIs. And they’ve worked well up to a point. But as frontends became more complex, and as data needs became more flexible, REST started to feel rigid.

You request a list of books, and the server gives you 20 fields, while your UI only needs two. Or worse, you need a nested structure of data, and you end up making multiple round-trip requests to stitch everything together.

This is where GraphQL comes to the rescue!  Let’s take a look at some of the key advantages GraphQL offers over traditional REST APIs:

1. Precise Data Fetching

One of GraphQL’s biggest strengths is its ability to allow clients to request exactly the data they need — nothing more, nothing less. In REST, an endpoint returns a fixed data structure, often including more information than the client requires, or requiring multiple requests to different endpoints to piece together the needed data. With GraphQL, clients can define the structure of the response in their queries, eliminating over-fetching and under-fetching problems and improving performance, especially on mobile networks.

2. Fewer Network Requests

In RESTful architectures, it’s common to make multiple API calls to different endpoints to gather related pieces of data. GraphQL consolidates these needs into a single query, no matter how complex the data relationships are. This not only reduces the number of network requests but also simplifies client-side logic and reduces latency.

3. Strongly Typed Schema and Introspection

GraphQL APIs are organized around a strongly typed schema that serves as a contract between the client and server. This schema is self-documenting (no need for tools like Swagger) and introspectable, meaning developers can easily explore available queries, types, and mutations without needing external documentation. This leads to better developer experience, faster onboarding, and improved collaboration across teams.

4. Evolution Without Versioning

Maintaining multiple versions of a REST API can be cumbersome. Every time the API needs to evolve, developers often create a new version (e.g., v1, v2), leading to fragmentation. GraphQL avoids this problem by enabling fields to be added or deprecated without breaking existing clients. Because clients request only the fields they need, adding new fields won’t affect older queries, allowing for a more fluid and sustainable evolution of APIs.

5. Better Developer Tools and Ecosystem

Thanks to its introspective nature, GraphQL boasts a rich ecosystem of powerful developer tools. Tools like GraphiQL, Apollo Studio, Altair GraphQL Client and Postman’s GraphQL features offer in-browser IDEs, automatic documentation, query validation, and performance monitoring. This level of tooling not only enhances productivity but also improves debugging and testing workflows:

Hello World - GraphQL Server/Client example

A Developer’s Guide to Smarter APIs

Let us build both a GraphQL Server and Client application.  As you can see, the Solution containing both Server, Client and a 3rd Project with the model definitions is quite slim:

.NET Solution for Server, Client and Model

For the Server/Backend we will use HotChocolate.

HotChocolate is designed with modern development practices in mind, making it easier to use and understand.  It supports a code-first approach, which can simplify the development process by allowing you to define your schema using C# classes. It comes with built-in support for features like subscriptions, filtering, and paginationHotChocolate is optimized for performance and can handle high loads efficiently.

Simple add its NewGet package:

add HotChocolate NuGet package

First, we need to define our domain objects. We will put them in a seperate project so that both Server and Client can adress them:

namespace Demo.Models
{
   [GraphQLDescription("An object of type book")]
   public class Book
   {
      [GraphQLDescription("The ID of the book")]
      public Guid Id { get; set; } = Guid.NewGuid();

      [GraphQLDescription("The title of the book")]
      public string Title { get; set; }

      [GraphQLDescription("The ISBN number of the book")]
      public string ISBN { get; set; }

      [GraphQLDescription("The author of the book")]
      public Author Author { get; set; }
   }

   [GraphQLDescription("An object of type author")]
   public class Author
   {
      [GraphQLDescription("The Name of the author")]
      public string Name { get; set; }
   }
}

As you might have noticed, we can comment each and any property with GraphQLDescription.  Next, we will add queries to our GraphQL Backend ("server").  For demonstration simplicity we just mock some data:

namespace Demo.BackendQL.Queries
{
   [GraphQLDescription("All available GraphQL queries")]
   public class Query
   {
      [GraphQLDescription("Get a specifc book by its ID")]
      public Book GetBook(
         [GraphQLDescription("Param1: The ID of the book as GUID")] Guid id) =>
      new()
      {
         Title = "C# for Dummies",
         ISBN = "978-3-16-148410-0",
         Author = new Author
         {
            Name = "Jon Doe"
         }
      };

      [GraphQLDescription("Get list of all authors")]
      public List<Author> GetAllAuthors() =>
      [
         new Author { Name = "Jon Doe" },
         new Author { Name = "Walter Smith" },
         new Author { Name = "Brian Schmid" },
      ];
   }
}

Next, we need to add the services required by HotChocolate to our Dependency Injection container and map the GraphQL endpoint in Program.cs like:

using Demo.BackendQL.Queries;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGraphQLServer()
                .AddQueryType<Query>();

var app = builder.Build();
app.MapGraphQL();
app.Run();

Then simply hit F5 or performe a dotnet -run on the Backend and point you webbrowser to https://localhost:7199/graphql/ (or whatever port you have assigned in your launchSettings.json file) and the Backend-Browser will show up:

For more informations please visit the HotCocolate documentation.

You can now enter your first GraphQL query in the left field of the editor:

{
  book(id: "00000000-0000-0000-0000-000000000000") {
    id
    isbn
    title
    author {
      name
    }
  }
}

and you will get all fields for the (only) book in our "datastore" in the "Response" field of the editor:

Query all Fields for the Book with the given ID

Congratulation, your GraphQL server is up and running!

Consuming the GraphQL server with our own Client

Next, we will setup a simple .NET Console Application that acts as our client.  Create a new Console Application and add following NuGet packages:

needed NuGet Packages for the GraphQL Client

Since our Backend exposes two "functions", one that returns a single object and one that returns a List, we need to setup two queries and of course need to initialize the GraphQL endpoint:

using GraphQL;
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.Newtonsoft;
using ModernHttpClient;
using Newtonsoft.Json;

namespace Demo.Client.GraphQL
{
    public class QueryGraphQL
    {
        private static GraphQLHttpClient graphQLHttpClient;

        static QueryGraphQL()
        {
            // adjust the URI according to the applicationUrl in
// Demo.BackendQL/Properties/launchSettings.json
            var uri = new Uri("https://localhost:7199/graphql/");

            var graphQLOptions = new GraphQLHttpClientOptions
            {
                EndPoint = uri,
                HttpMessageHandler = new NativeMessageHandler(),
            };

            graphQLHttpClient = new GraphQLHttpClient(graphQLOptions, new NewtonsoftJsonSerializer());
        }

        public static async Task<T> ExceuteQueryAsyn<T>(string graphQLQueryType, string completeQueryString)
        {
            try
            {
                var request = new GraphQLRequest
                {
                    Query = completeQueryString
                };

                var response = await graphQLHttpClient.SendQueryAsync<object>(request);

                var stringResult = response.Data.ToString();
                stringResult = stringResult.Replace($"\"{graphQLQueryType}\":", string.Empty);
                stringResult = stringResult.Remove(0, 1);
                stringResult = stringResult.Remove(stringResult.Length - 1, 1);

                var result = JsonConvert.DeserializeObject<T>(stringResult);

                return result;
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        public static async Task<List<T>> ExceuteQueryReturnListAsyn<T>(string graphQLQueryType, string completeQueryString)
        {
            try
            {
                var request = new GraphQLRequest
                {
                    Query = completeQueryString
                };

                var response = await graphQLHttpClient.SendQueryAsync<object>(request);

                var stringResult = response.Data.ToString();
                stringResult = stringResult.Replace($"\"{graphQLQueryType}\":", string.Empty);
                stringResult = stringResult.Remove(0, 1);
                stringResult = stringResult.Remove(stringResult.Length - 1, 1);

                var result = JsonConvert.DeserializeObject<List<T>>(stringResult);

                return result;
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }
}

In Program.cs of our Client we make three different calls:

  • return a single book for the given ID that returns just the title
  • return a single book for the given ID with all its fields
  • return the list of all authors
using Demo.Client.GraphQL;
using Demo.Models;

var query = new QueryGraphQL();

// just query the title for the given book
await GetSingleBookJustTheTitle(new Guid("d1b0f2a4-3c5e-4b8e-9f7c-0a6d1f3b2c5d"));

// query all fields for the given book
await GetSingleBookAllFields(new Guid("d1b0f2a4-3c5e-4b8e-9f7c-0a6d1f3b2c5d"));

// list all authors
await ListAllAuthors();

Console.WriteLine("Done.");

async Task GetSingleBookJustTheTitle(Guid guid)
{
    try
    {
        string completeQuery = $"query{{book(id: \"{guid}\"){{title}}}}";
        string graphQLQueryType = "book";
        var result = await QueryGraphQL.ExceuteQueryAsyn<Book>(graphQLQueryType, completeQuery);
        Console.WriteLine(result.Title);
        Console.WriteLine("");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

async Task GetSingleBookAllFields(Guid guid)
{
    try
    {
        string completeQuery = $"query{{book(id: \"{guid}\"){{id title isbn author{{name}}}}}}";
        string graphQLQueryType = "book";
        var result = await QueryGraphQL.ExceuteQueryAsyn<Book>(graphQLQueryType, completeQuery);
        Console.WriteLine($"ID:      {result.Id}");
        Console.WriteLine($"ISBN:    {result.ISBN}");
        Console.WriteLine($"Title:   {result.Title}");
        Console.WriteLine($"Author:  {result.Author.Name}");
        Console.WriteLine("");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

async Task ListAllAuthors()
{
    try
    {
        string completeQuery = "query{allAuthors{name}}";
        string graphQLQueryType = "allAuthors";

        var result = await QueryGraphQL.ExceuteQueryReturnListAsyn<Author>(graphQLQueryType, completeQuery);
        foreach (var author in result)
        {
            Console.WriteLine(author.Name);
        }
        Console.WriteLine("");
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

If you've setup everything right you will get this output:

GraphQL Client up and running

Conclusion

While REST is not going away anytime soon and remains perfectly suited for many use cases, GraphQL offers significant advantages for applications where flexibility, efficiency, and rapid development cycles are critical. By enabling more precise data fetching, reducing the number of network requests, and improving the overall developer experience, GraphQL is helping to shape the future of API development.

Download Sample Source Code