What is GraphQL?

  • Yet another communication standard
  • Mainly for client ⇔ server
  • GraphQL is typically served over HTTP
  • GraphQL services typically respond using JSON (yet not required by the spec)

Data model

With GraphQL you think of data as a graph of nodes.

flowchart LR
subgraph People
direction TB
A["People[0]"]
B["People[1]"]
C["People[2]"]
D["People[3]"]
end
A --> ID
A --> Name
A --> DateOfBirth
A --> Hobbies
Hobbies --> S0["Hobbies[0]"]
Hobbies --> S1["Hobbies[1]"]
S0 --> A0[Description]
S1 --> A1[Description]

Query language

GraphQL has its own language to query data

{
  people {
    name
  }
}
{
  people(id: 1) {
    title,
    hobbies {
      description
    }
  }
}

Why?

  • GraphQL gives power to the client.
  • The client can specify what they want exactly for a specific use-case.
    • The server needs to be implemented in a way that supports the queries.
  • This leads to very targeted queries fetching only the information that is needed.

Add GraphQL to an ASP.NET Core Web API

Using HotChocolate1

NuGet:

Install-Package HotChocolate.AspNetCore
Install-Package HotChocolate.Data
Install-Package GraphQL.Server.Transports.AspNetCore

Add a query

public class PersonQuery  
{  
    public IQueryable<Person> GetPeople =>  
		new List<Person>().AsQueryable();    
}

The methods can return any data. We just return an empty list here to get started.


Register with dependency injection

// Program.cs
 
// Type Query points to Query class (previous slide)
builder.Services.AddGraphQLServer().AddQueryType<PersonQuery>()
// ...
app.MapGraphQL(path: "/graphql");

Run and verify

Run application and go to route /graphql in the browser.

Create a new document and try out the following query.

query MyQuery {
  getPeople {
    name
  }
}

Should yield result:

{
  "data": {
    "getPeople": []
  }
}

Add connection to database

public class PersonQuery  
{  
    [UseProjection]  
    [UseFiltering]  
    [UseSorting]  
    public IQueryable<Person> GetPeople(
	    [Service] PeopleDbContext context) =>  
	        context.People;  
}
// Program.cs
builder.Services.AddGraphQLServer().AddQueryType<PersonQuery>()
  .AddProjections().AddFiltering().AddSorting();

Verify again

query GetPeople {
  people {
    name
  }
}

returns

{
  "data": {
    "people": [
      {
        "name": "Hugo"
      }
    ]
  }
}

Get element by ID

public class Query  
{    
  [UseFirstOrDefault]  
  [UseProjection]  
  public IQueryable<Person?> GetPerson(
    [Service] PeopleDbContext context, 
    int id) =>  
      context.People.Where(r => r.Id == id); 
}
query GetPerson {
  person(id: 1) {
    name
  }
}
{
  "data": {
    "person": {
      "name": "Hugo"
    }
  }
}

Add GraphQL to a Blazor WASM Client

Using StrawberryShake2

# in root folder of solution
dotnet new tool-manifest
dotnet tool install StrawberryShake.Tools
 
# in Blazor Client project
Install-Package StrawberryShake.Blazor

Generate client

# server needs to run for this command
# run command in root folder of solution
# use whatever url your server is serving from
dotnet graphql init https://localhost:7232/graphql/ `
-n PeopleClient ` # give your generated client a name
-p PeopleBlazorClient # point to the client project

Customize namespace

// .graphqlrc.json
{  
  "schema": "schema.graphql",  
  "documents": "**/*.graphql",  
  "extensions": {  
    "strawberryShake": {  
      "name": "PeopleClient",  
      "namespace": "PeopleApp.Client.GraphQL",  
      "url": "https://localhost:7232/graphql/",  
      //...
    }  
  }  
}
 

Write query definition

# GetPeople.graphql
query GetPeople {  
  people {  
    name, 
    id  
  }  
}

Build project

dotnet build

Register client with dependency injection

// Program.cs
builder.Services  
    .AddPeopleClient()  
    .ConfigureHttpClient(client => 
      client.BaseAddress = new Uri(
        "https://localhost:7232/graphql"));

Query data

@inject PeopleClient PeopleClient
// ...
await PeopleClient.GetPeople.ExecuteAsync();

Mutations

GraphQL Mutations let you modify data.

mutation MyMutation {
  addPerson(person: {
    name: "Hugo",
    targetAmount: 1337,
    dateOfBirth: "2023-03-01T00:00:00.000+01:00",
  }) {
    name,
    id
  }
}

You send the data and have control of what you get back.


Server Implementation

public class Mutation  
{  
    public async Task<PersonListItemDto> AddPerson(
      [Service] IRepository<Person> personRepository, 
      AddPersonDto person)  
    {  
        return await personRepository
          .AddAsync<AddPersonDto, PersonListItemDto>(person);  
    }  
}

It is recommended to create specific types for adding items (e.g. without ID property).


Registration

//Program.cs
builder.Services  
    .AddGraphQLServer()
    // ... your queries
    .AddMutationType<Mutation>();

Update Client

# run from Client directory
# server needs to run
# correct server address needs to be specified in .graphqlrc
dotnet graphql update

Add Definition for Mutation

# AddPerson.graphql
mutation AddPerson($personInput: AddPersondDtoInput!) {  
    addPerson(person: $personInput) {
        id  
    }  
}

Build again

dotnet build

Use client to modify data

// NewPerson.razor
@inject PersonClient PersonClient
// ...
await PersonClient.AddPerson.ExecuteAsync(
  new AddPersonDtoInput()  
  {  
    Name = _name,  
    DateOfBirth = _dateOfBirth,  
    SavingStart = _startDate.Value
  });

Subscriptions

GraphQL offers an easy way to subscribe to events.
The Server can notify the client of changes via a websocket connection.

sequenceDiagram
	Client->>+Server: Subscribe()
	Note right of Server: Subscription is initiated via HTTP
	Server-->>-Client: ack
	Server->>Client: Update()
	Server->>Client: Update()
	Note right of Server: Updates are sent via WebSocket

Server Implementation

public class Subscription  
{  
  [Subscribe]  
  public PersonListItemDto PersonAdded(
    [EventMessage] PersonListItemDto person) 
    => person;  
}

Send subscription message in Mutation

public async Task<PersonListItemDto> AddPerson(  
    [Service] IRepository<Person> personRepository,   
    [Service] ITopicEventSender sender,  
    AddPersonDto person)    
{    
    var addedPerson = await personRepository  
        .AddAsync<AddPersonDto, PersonListItemDto>(person);  
    await sender.SendAsync(
      nameof(Subscription.PersonAdded), addedPerson);  
    return addedPerson;  
}

Registration

// Program.cs
builder.Services  
  .AddGraphQLServer()  
  // ... queries and mutations
  .AddInMemorySubscriptions()
  .AddSubscriptionType<Subscription>();
 
// ...
 
app.UseRouting();
app.UseWebSockets(); // add this line below UseRouting

Test subscriptions

subscription MySubscription {
  personAdded {
    id,
    name
  }
}

Client implementation

# run from Client directory
# server needs to run
# correct server address needs to be specified in .graphqlrc
dotnet graphql update
 
# Add NuGet Package
Install-Package StrawberryShake.Transport.WebSockets

Add subscription definition

# SubscribePersonAdded.graphql
subscription SubscribePersonAdded {  
    personAdded {  
        id,  
        name 
    }  
}

Build project to regenerate client afterwards.


Configuration

// Program.cs
var graphQlUri = new Uri(
  builder.HostEnvironment.BaseAddress + "graphql");  
var graphQlWebSocketUri = new UriBuilder(graphQlUri)  
{  
    Scheme = Uri.UriSchemeWss  
}.Uri;
 
builder.Services.AddPersonClient()  
  .ConfigureHttpClient(client => 
    client.BaseAddress = graphQlUri)  
  .ConfigureWebSocketClient(client => 
    client.Uri = graphQlWebSocketUri);

Use subscription in client

// People.razor
@implements IDisposable
// ...
private IDisposable? personAddedSubscription;  
  
protected override async Task OnInitializedAsync()  
{  
  await LoadDataAsync();  
  personAddedSubscription = PersonClient  
    .SubscribePersonAdded  
    .Watch()  
      .Subscribe(message =>  
      {  
        var dto = new PersonListItemDto(
          message.Data.PersonAdded.Id,  
          message.Data.PersonAdded.Title,  
          message.Data.PersonAdded.TargetAmount,  
          message.Data.PersonAdded.Description);  
	    people.Add(dto);  
		StateHasChanged();  
	  });  
}
 
public void Dispose()  
{  
    personAddedSubscription?.Dispose();  
}

Footnotes

  1. Get started with GraphQL in .NET Core

  2. Get started with Strawberry Shake and Blazor