Friday, May 9, 2025

What is GraphQL and How Can It Be Used?

 


Getting Started with GraphQL in .NET 8

A practical, end‑to‑end primer using Hot Chocolate & Strawberry Shake   


Why GraphQL?

If you’ve built—or consumed—REST APIs you’ve felt the pain of under‑ and over‑fetching:

* Needing three endpoints to hydrate one screen
* Getting twenty properties when you only need two

GraphQL fixes that with a single, flexible endpoint where the client asks for exactly what it needs and nothing more. It also supports real‑time subscriptions (think dashboards, chat, IoT feeds) without bolting on a separate signal‑plane.


The .NET ecosystem

  • Hot Chocolate – the premier GraphQL server for ASP.NET Core, now on v14 ChiliCream

  • Strawberry Shake – a type‑safe, code‑gen GraphQL client for any .NET platform ChiliCream

They’re both from the same maintainers (ChilliCream) and play together perfectly.


1 Server Setup with Hot Chocolate

Target: .NET 8 Minimal‑API web app

  1. Add packages


dotnet add package HotChocolate.AspNetCore dotnet add package HotChocolate.Subscriptions
  1. Program.cs


var builder = WebApplication.CreateBuilder(args); builder.Services .AddGraphQLServer() .AddQueryType<Query>() // read‑only operations .AddMutationType<Mutation>() // writes .AddSubscriptionType<Subscription>() // real‑time events .AddInMemorySubscriptions(); // dev‑friendly message broker var app = builder.Build(); app.MapGraphQL(); // maps /graphql + WebSockets app.Run();

What those calls do

CallPurpose (one‑liner)
AddGraphQLServer()Registers the GraphQL middleware, execution engine, schema services, and Banana Cake Pop IDE.
AddQueryType<T>() / AddMutationType<T>() / AddSubscriptionType<T>()Maps your C# classes to the root Query, Mutation, and Subscription types in the schema.
AddInMemorySubscriptions()Adds an in‑memory pub/sub broker so your mutations can raise events the subscriptions will receive. Ideal for development; swap for Redis, Kafka, etc. in production.
  1. Domain & resolver classes


public record Book(int Id, string Title, string Author); public record Movie(int Id, string Title, string Director); public class Query { // Simple demo query public IEnumerable<Book> GetBooks() => _books; private static readonly List<Book> _books = [ new(1,"Clean Code","Robert C. Martin") ]; } public class Mutation { public async Task<Book> AddBook( string title, string author, [Service] ITopicEventSender sender) { var book = new Book(Random.Shared.Next(1000, 9999), title, author); await sender.SendAsync(nameof(Subscription.BookAdded), book); return book; } // AddMovie omitted for brevity – same pattern } public class Subscription { [Subscribe, Topic] public Book BookAdded([EventMessage] Book b) => b; }

Run the project and browse to /graphql. Banana Cake Pop appears with schema introspection and an interactive playground.


mutation { addBook(title:"Dune", author:"Frank Herbert") { id title author } }

Open a second tab:


subscription { bookAdded { id title author } }

Add a book in tab 1 → the event streams into tab 2. ✨


2 Consuming the API from a .NET Client

Option A – Quick & Dirty HttpClient

Great for scripts/tests:


var http = new HttpClient { BaseAddress = new("https://localhost:5001/graphql") }; var gql = new { query = @"query { books { id title author } }" }; var res = await http.PostAsJsonAsync("", gql); var json = await res.Content.ReadAsStringAsync(); Console.WriteLine(json);

(No strong typing, no IDE help.)

Option B – Strawberry Shake (type‑safe)

  1. Install tooling (once per machine)


dotnet tool install -g StrawberryShake.Tools
  1. Create client project


dotnet new console -n BooksClient cd BooksClient dotnet add package StrawberryShake.Transport.WebSockets
  1. Init & generate


dotnet graphql init http://localhost:5001/graphql dotnet graphql generate

The tool:

  • Downloads the schema

  • Generates C# models & operations

  • Adds IBooksClient to DI

  1. Use it


await using var host = Host .CreateDefaultBuilder() .ConfigureServices(s => s .AddBooksClient() // extension generated for you .ConfigureHttpClient(c => c.BaseAddress = new("https://localhost:5001/graphql"))) .Build(); var client = host.Services.GetRequiredService<IBooksClient>(); // mutation var add = await client.AddBook.ExecuteAsync("Foundation", "Isaac Asimov"); Console.WriteLine($"Created: {add.Data!.AddBook.Id}"); // subscription await foreach (var ev in client.BookAdded.Watch()) { Console.WriteLine($"New book: {ev.Data!.BookAdded.Title}"); }

You now have compile‑time safety, auto‑completed queries, and integrated WebSocket subscriptions.


3 Where to Go Next

  • Pagination & filtering – Hot Chocolate supports Relay‑style cursors, projections, and filtering middleware.

  • Authorization – Decorate resolver methods with [Authorize] or integrate ASP.NET Core policies.

  • Production subscriptions – Swap AddInMemorySubscriptions() for Redis or Kafka back‑planes.

  • Monitoring – Hot Chocolate exposes events for OpenTelemetry tracing and metrics ChiliCream.


## Bonus: Subscriptions in Depth — Real‑Time GraphQL for your .NET apps
(drop this straight into the post after the “Where to Go Next” section)


### Why you care 🔔

  • Live dashboards & tickers – push deltas instead of polling REST.

  • Chat, collaboration, IoT – duplex or server‑push communication over one endpoint.

  • Cleaner backend code – one mutation can publish an event that any number of clients subscribe to.


### 1 Transports: WebSocket vs SSE

TransportDirectionSpecs Hot Chocolate SupportsNotes
WebSocketsfull‑duplexgraphql‑ws (recommended) and legacy subscriptions‑transport‑ws ChiliCreamWorks everywhere, but needs 80/443 → WS upgrade.
SSE (GraphQL‑SSE)server→clientapplication/stream+json via the GraphQL‑SSE protocol (v13+) ChiliCreamFirewalls love it; great for dashboards where clients don’t need to talk back.

In ASP.NET Core you get WebSockets by default (app.UseWebSockets()), and Hot Chocolate wires up SSE automatically if the client requests it.


### 2 Choosing a Pub/Sub Provider

ProviderWhen to useNuGet & DI
In‑Memory (default)Local dev / single nodeHotChocolate.Subscriptions.AddInMemorySubscriptions()
RedisMulti‑node, cheap & battle‑testedHotChocolate.Subscriptions.Redis.AddRedisSubscriptions(o ⇒ ConnectionMultiplexer.Connect("redis:6379")) ChiliCream
Kafka / Azure SB / RabbitMassive fan‑out or enterprise infraCommunity packages or write ITopicEventSender / ITopicEventReceiver adapters
csharp
builder.Services .AddGraphQLServer() .AddQueryType<Query>() .AddMutationType<Mutation>() .AddSubscriptionType<Subscription>() .AddRedisSubscriptions(o => ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")));

### 3 Securing Subscriptions

  1. Handshake auth – add a JWT or bearer token in the initial connection_init payload or WebSocket/SSE headers.

  2. Per‑field rules – decorate resolver methods with [Authorize] just like queries/mutations.

  3. Lifecycle hooks – implement ISocketSessionInterceptor to vet each connect / init / subscribe request (e.g., reject if token expired) ChiliCream.


### 4 Banana Cake Pop quick test

graphql
# tab 1 – subscription subscription { bookAdded { id title author } } # tab 2 – mutation mutation { addBook(title:"Dune", author:"Frank Herbert") { id } }

Tab 1 instantly receives the payload—no refresh required.


### 5 Strongly‑Typed Client Subscriptions with Strawberry Shake

Works for console, MAUI, WPF, Blazor, Unity…anything .NET Standard.

tool & package prerequisites (if you haven’t already):

bash
dotnet tool install -g StrawberryShake.Tools dotnet add package StrawberryShake.Transport.WebSockets

#### a. Tell the code‑gen to use WebSockets

Create or extend .graphqlrc.json in your client project:

json
{ "schema": "http://localhost:5001/graphql", "documents": "Operations/**/*.graphql", "extensions": { "strawberryShake": { "namespace": "BooksClient", "transportProfiles": { "Default": { "http": { "endpoint": "http://localhost:5001/graphql" }, "webSocket": { "endpoint": "ws://localhost:5001/graphql" } } } } } }

#### b. Define the operation

graphql
# Operations/BookAdded.graphql subscription BookAdded { bookAdded { id title author } }

#### c. Generate + wire up

bash
dotnet graphql generate
csharp
await using var host = Host.CreateDefaultBuilder() .ConfigureServices(s => s .AddBooksClient() // generated ext. .ConfigureWebSocketClient(c => c.Uri = new("ws://localhost:5001/graphql"))) .Build(); var client = host.Services.GetRequiredService<IBooksClient>(); await foreach (var ev in client.BookAdded.Watch()) { Console.WriteLine($"📚 {ev.Data!.BookAdded.Title} just dropped!"); }

Tip: To multiplex queries + subscriptions over the same socket, set WebSocketClientOptions.ConnectionInitializer to send your auth token during connect.


### 6 Production Checklist ✅

AreaGotchaFix
Load‑balancingSticky sessions required for WSUse Redis/Kafka back‑plane or SSE (stateless).
Firewalls / proxiesWS blockedFall back to SSE (Accept: text/event-stream).
ReconnectionsMobile devices sleepEnable Strawberry Shake’s retry/reconnect policies.
Tracing & metricsStreams can be chattyTurn on Hot Chocolate OpenTelemetry integration and sample aggressively.

### Wrap‑up

With these pieces you now have a full‑stack real‑time pipeline:

  • Server – Hot Chocolate with Redis (or in‑memory) subscriptions

  • Client – Strawberry Shake streams that deserialize directly into C# records

Add a single line of code at each mutation to eventSender.SendAsync(...) and every connected UI updates itself—no SignalR, no polling, no fuss.


Final Thoughts

GraphQL isn’t a silver bullet, but when you need:

  • chat‑style real‑time updates

  • mobile apps that loathe over‑fetching

  • complex UIs that would otherwise juggle many REST calls

…it’s a game‑changer.

With Hot Chocolate the server setup is minutes, not hours, and Strawberry Shake gives you a first‑class .NET experience on the client. Give it a try on your next side project and see how it feels—chances are you’ll never craft a fleet of brittle REST endpoints again.

Happy querying! 🚀

No comments:

Post a Comment

New Features in .Net 10

🚀 Runtime Enhancements Stack Allocation for Small Arrays The Just-In-Time (JIT) compiler now optimizes memory usage by stack-allocating s...