GraphQL with .NET Core (Part - XII: Relay)
source link: https://www.fiyazhasan.me/graphql-with-net-core-part-xii-relay/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
GraphQL with .NET Core (Part - XII: Relay)
Relay is a JavaScript framework for building data-driven React applications. It's a scalable GraphQL client.
To make a Relay compliant GraphQL server, you have to follow this specification. Fortunately, GraphQL-Dotnet community has provided a library to take on the heavy burden.
The current Nuget package for graphql-dotnet/relay
doesn't work with the latest version of GraphQL (4.2.2). So, I cloned the GitHub repository and created a Nuget
package out of it. The package is available at the root of the sample repository of this blog series.
The graphql-dotnet/relay
library includes a few GraphQL types and helpers for creating Connections
, Mutations
, and Nodes
. After adding the library, you have to register Relay
specific GraphQL
types with the ASP.NET Core
's dependency injection (DI) container,
services.AddTransient(typeof(ConnectionType<>))
.AddTransient(typeof(EdgeType<>))
.AddTransient<NodeInterface>()
.AddTransient<PageInfoType>();
Startup.csThe two core assumptions that Relay makes about a GraphQL server are that it provides:
- A mechanism for refetching an object.
- A description of how to page through connections.
Object Identification
Object identification feature is enabled by using the NodeGraphType
. The class extends ObjectGraphType
and adds a GetById
field, which helps Relay (via the node()
Query) to refetch nodes when it needs to. It also provides an Id()
for defining a global id for the node. The incoming and outgoing ids are all Base64
encoded.
To make the ItemType
a node type, ItemType
is extended from AsyncNodeGraphType<Item>
,
public class ItemType : AsyncNodeGraphType<Item>
{
private readonly IRepository _repository;
public ItemType(IRepository repository)
{
_repository = repository;
Id(p => p.Id);
Field(i => i.Tag);
Field(i => i.Title);
Field(i => i.Price);
}
public override async Task<Item> GetById(string id)
{
return await _repository.GetItemById(Convert.ToInt32(id));
}
}
ItemType.csMutations
The library also provides a MutationInputGraphType
. It's an abstraction over the regular mutation implemented using Field
. The core implementation is as following,
public class MutationGraphType : ObjectGraphType
{
public MutationGraphType()
{
Name = "Mutation";
}
public FieldType Mutation<TMutationInput, TMutationType>(string name)
where TMutationType : IMutationPayload<object>
where TMutationInput : MutationInputGraphType
{
return Field(
name: name,
type: typeof(TMutationType),
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<TMutationInput>> { Name = "input" }
),
resolve: c =>
{
var inputs = c.GetArgument<Dictionary<string, object>>("input");
return ((TMutationType)c.FieldDefinition.ResolvedType).MutateAndGetPayload(new MutationInputs(inputs), c);
}
);
}
}
MutationGraphType.cs (graphql-dotnet/relay)We have to extend the GameStoreMutation
from MutationInputGraphType
in order to use the Mutation<IMutationPayload<object>, MutationInputGraphType>
,
public class GameStoreMutation : MutationGraphType
{
public GameStoreMutation()
{
Mutation<CreateItemInput, CreateItemPayload>("createItem");
}
}
GameStoreMutation.csFollowings are the CreateItemInput
and CreateItemPayload
files,
public class CreateItemInput : MutationInputGraphType
{
public CreateItemInput()
{
Name = "CreateItemInput";
Field<NonNullGraphType<StringGraphType>>("tag");
Field<NonNullGraphType<StringGraphType>>("title");
Field<NonNullGraphType<DecimalGraphType>>("price");
}
}
CreateItemInput.csMutationInputGraphType
adds a clientMutationId
field on top of InputGraphType
. Although clientMutationId
was required for Classic Relay
, Modern Relay
doesn't need that anymore.
public class CreateItemPayload : MutationPayloadGraphType<object, Task<object>>
{
private readonly IRepository _repository;
public CreateItemPayload(IRepository repository)
{
_repository = repository;
Field<ItemType>("item");
}
public override async Task<object> MutateAndGetPayload(MutationInputs inputs, IResolveFieldContext<object> context)
{
string tag = inputs.Get<string>("tag");
string title = inputs.Get<string>("title");
decimal price = inputs.Get<decimal>("price");
Item item = await _repository.AddItem(new Item { Tag = tag, Title = title, Price = price });
return new
{
item
};
}
}
CreateItemPayload.csConnections
The pagination and data slicing model used in GraphQL is called the Connection Cursor Specification
. The base library graphql-dotnet/graphql-dotnet
provides utilities to work with Connection
. On top of it, graphql-dotnet/relay
adds additional utilities to make it work with Relay.
To make the items
field in the GameStoreQuery
a Connection
field, modify the code as followings,
Connection<ItemType>()
.Name("items")
.PageSize(10)
.ResolveAsync(async ctx =>
{
var loader = accessor.Context.GetOrAddLoader("GetAllItems", repository.GetItems);
return ConnectionUtils.ToConnection(await loader.LoadAsync().GetResultAsync(), ctx);
});
GameStoreQuery.csPractically, you would want to see a result of a mutation (add/update/delete) directly in your connection. In that case, you should return an EdgeType
in your mutation payload. For example, I would want to see the newly added item in my connection, I would modify the CreateItemPayload
as following,
public class CreateItemPayload : MutationPayloadGraphType<object, Task<object>>
{
private readonly IRepository _repository;
public CreateItemPayload(IRepository repository)
{
_repository = repository;
Field<EdgeType<ItemType>>("itemEdge");
}
public override async Task<object> MutateAndGetPayload(MutationInputs inputs, IResolveFieldContext<object> context)
{
string tag = inputs.Get<string>("tag");
string title = inputs.Get<string>("title");
decimal price = inputs.Get<decimal>("price");
Item item = await _repository.AddItem(new Item { Tag = tag, Title = title, Price = price });
return new
{
ItemEdge = new Edge<Item>
{
Node = item,
Cursor = ConnectionUtils.CursorForObjectInConnection(await _repository.GetItems(), item)
},
};
}
}
CreateItemPayload.csRepository Link
Important Links
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK