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.AspNetCoreAdd 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.BlazorGenerate 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 projectCustomize 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 buildRegister 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 updateAdd Definition for Mutation
# AddPerson.graphql
mutation AddPerson($personInput: AddPersondDtoInput!) {
addPerson(person: $personInput) {
id
}
}Build again
dotnet buildUse 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 UseRoutingTest 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.WebSocketsAdd 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();
}