website/src/docs/hotchocolate/v16/api-reference/apollo-federation.md
For more about Apollo Federation concepts, see the Apollo Federation documentation. Many of the core principles referenced here are documented there.
Hot Chocolate includes an implementation of the Apollo Federation v1 specification for creating Apollo Federated subgraphs. Through Apollo Federation, you can combine multiple GraphQL APIs into a single API for your consumers.
This page describes the syntax for creating an Apollo Federated subgraph using Hot Chocolate and relates the implementation specifics to their counterpart in the Apollo Federation docs. It does not provide a thorough explanation of Apollo Federation core concepts or describe how you create a supergraph to stitch together subgraphs.
You can find example projects in Hot Chocolate examples.
Install the HotChocolate.ApolloFederation package:
Register the Apollo Federation services:
builder
.AddGraphQL()
.AddApolloFederation();
An entity is an object type that can resolve its fields across multiple subgraphs. The following examples use a Product entity.
public class Product
{
[ID]
public string Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
}
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
}
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor.Field(product => product.Id).ID();
}
}
Coming soon
</Schema> </ExampleTabs>Define a key for the entity. A key serves as an identifier that uniquely locates an individual record. This is typically something like a primary key, SKU, or account number.
<ExampleTabs> <Implementation>Use the [Key] attribute on the property or properties that serve as the key:
public class Product
{
[ID]
[Key]
public string Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
}
Use the Key() method on the descriptor:
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor.Field(product => product.Id).ID();
descriptor.Key("id");
}
}
Coming soon
</Schema> </ExampleTabs>Define an entity reference resolver so that the supergraph can resolve the entity across subgraphs. Every subgraph that contributes at least one unique field to an entity must define a reference resolver.
<ExampleTabs> <Implementation>A reference resolver must be annotated with [ReferenceResolver] and must be a public static method within the type it resolves:
public class Product
{
[ID]
[Key]
public string Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
[ReferenceResolver]
public static async Task<Product?> ResolveReference(
string id,
ProductBatchDataLoader dataLoader)
{
return await dataLoader.LoadAsync(id);
}
}
Key details about [ReferenceResolver] methods:
[Key] attribute. For example, if the key field is id: ID!, the parameter must be string id.T?) when using nullable reference types.public class Product
{
[Key]
public string Id { get; set; }
[Key]
public int Sku { get; set; }
[ReferenceResolver]
public static Product? ResolveReferenceById(string id)
{
// Locate the Product by its Id
}
[ReferenceResolver]
public static Product? ResolveReferenceBySku(int sku)
{
// Locate the product by SKU
}
}
Chain ResolveReferenceWith() off of the Key() method:
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor.Field(product => product.Id).ID();
descriptor.Key("id")
.ResolveReferenceWith(_ => ResolveByIdAsync(default!, default!));
}
private static Task<Product?> ResolveByIdAsync(
string id,
ProductBatchDataLoader dataLoader)
{
return dataLoader.LoadAsync(id);
}
}
Key details:
Key() field set.T?).Key().Coming soon
</Schema> </ExampleTabs>We recommend using a DataLoader in reference resolvers. This helps avoid the N+1 problem.
Register the type in the GraphQL schema:
<ExampleTabs> <Implementation>builder
.AddGraphQL()
.AddApolloFederation()
.AddType<Product>();
builder
.AddGraphQL()
.AddApolloFederation()
.AddType<ProductType>();
Coming soon
</Schema> </ExampleTabs>Entities with a reference resolver can be queried through the auto-generated _entities query at the subgraph level:
query {
_entities(representations: [{ __typename: "Product", id: "<id value>" }]) {
... on Product {
id
name
price
}
}
}
The
_entitiesfield is an internal implementation detail of Apollo Federation. API consumers should not use it directly. It is documented here for testing and validation purposes.
To reference an entity defined in another subgraph, create a service type reference in a separate API project. The GraphQL type name must match, and the extended type must include at least one key that matches the source graph.
<ExampleTabs> <Implementation>[ExtendServiceType]
public class Product
{
[ID]
[Key]
public string Id { get; set; }
}
builder
.AddGraphQL()
.AddApolloFederation()
.AddType<Product>();
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor.ExtendServiceType();
descriptor.Key("id");
descriptor.Field(product => product.Id).ID();
}
}
builder
.AddGraphQL()
.AddApolloFederation()
.AddType<ProductType>();
Coming soon
</Schema> </ExampleTabs>Create a type (e.g., Review) that references the entity, and add a reference resolver so the supergraph can traverse between the types:
[ExtendServiceType]
public class Product
{
[ID]
[Key]
public string Id { get; set; }
public async Task<IEnumerable<Review>> GetReviews(ReviewRepository repo)
{
return await repo.GetReviewsByProductIdAsync(Id);
}
[ReferenceResolver]
public static Product ResolveProductReference(string id) =>
new Product { Id = id };
}
public class ProductType : ObjectType<Product>
{
protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
{
descriptor.ExtendServiceType();
descriptor.Key("id")
.ResolveReferenceWith(_ => ResolveProductReference(default!));
descriptor.Field(product => product.Id).ID();
}
private static Product ResolveProductReference(string id) =>
new Product { Id = id };
}
Coming soon
</Schema> </ExampleTabs>With these changes, the supergraph supports traversing from a review to a product and from a product to its reviews:
query {
review(id: "foo") {
id
content
product {
id
name
price
reviews {
id
content
}
}
}
}
For creating a supergraph, see the Apollo Router documentation or @apollo/gateway documentation.