diff --git a/website/src/docs/docs.json b/website/src/docs/docs.json
index a8d6aa41e0c..cf25162a607 100644
--- a/website/src/docs/docs.json
+++ b/website/src/docs/docs.json
@@ -209,12 +209,18 @@
"title": "Attribute and Directive Reference"
},
{
- "path": "coming-from-apollo-federation",
- "title": "Coming from Apollo Federation"
- },
- {
- "path": "migrating-from-schema-stitching",
- "title": "Migrating from Schema Stitching"
+ "path": "migration",
+ "title": "Migration",
+ "items": [
+ {
+ "path": "coming-from-apollo-federation",
+ "title": "Coming from Apollo Federation"
+ },
+ {
+ "path": "migrating-from-schema-stitching",
+ "title": "Migrating from Schema Stitching"
+ }
+ ]
}
]
},
diff --git a/website/src/docs/fusion/v16/getting-started.md b/website/src/docs/fusion/v16/getting-started.md
index f9a58c35de8..4d2113fde3a 100644
--- a/website/src/docs/fusion/v16/getting-started.md
+++ b/website/src/docs/fusion/v16/getting-started.md
@@ -2,23 +2,36 @@
title: "Getting Started"
---
-# Getting Started with HotChocolate Fusion
+# Getting Started with Hot Chocolate Fusion
+
+In this tutorial, you will build and run a small Fusion setup locally.
+
+By the end, you will have:
+
+- A Products subgraph on `http://localhost:5001/graphql`
+- A Reviews subgraph on `http://localhost:5002/graphql`
+- A Fusion gateway on `http://localhost:5000/graphql`
+- One composed API that clients query through the gateway
+
+If you want the broader conceptual background first, read [Overview](/docs/fusion/v16).
## What Is Fusion and Why Use It?
-HotChocolate Fusion lets you split a GraphQL API across multiple independent services -- called **subgraphs** -- and present them to clients as a single, unified schema (called a **composite schema** in the spec). Each subgraph contributes types and fields to the overall graph, runs in its own process, and can be developed, deployed, and scaled independently. A **gateway** sits in front of all your subgraphs, receives client queries, routes each part of the query to the subgraphs that own the requested fields, and combines the results. From the client's perspective, they are querying a single GraphQL endpoint with one schema -- the gateway handles everything transparently.
+Hot Chocolate Fusion lets you split a GraphQL API across multiple independent services, called **subgraphs**, and present them to clients as a single, unified schema (called a **composite schema** in the spec). Each subgraph contributes types and fields to the overall API, runs in its own process, and can be developed, deployed, and scaled independently.
+
+A **gateway** sits in front of all your subgraphs, receives client queries, routes each part of the query to the subgraphs that own the requested fields, and combines the results. From the client's perspective, they are querying a single GraphQL endpoint with one schema. The gateway handles everything transparently.
-Instead of building one monolithic GraphQL server that knows about every domain in your system, you let each team own and ship their own GraphQL service. Fusion handles the hard part -- composing those services into a single schema that clients query without knowing (or caring) how many services are behind it.
+Instead of building one monolithic GraphQL server that knows about every domain in your system, you let each team own and ship their own GraphQL service. Fusion handles the hard part by composing those services into a single schema that clients query without knowing (or caring) how many services are behind it.
-**When should you use Fusion?** If your GraphQL API is served by a single HotChocolate server and that works for your team, there is no reason to add the complexity of multiple subgraphs. Fusion becomes valuable when your API spans multiple domains (products, reviews, accounts, shipping), when different teams own different parts of the graph, or when you need to deploy and scale parts of your API independently. If you find yourself wanting to break a growing monolith into smaller, team-owned services without forcing clients to stitch together calls to multiple endpoints, Fusion is the tool for the job.
+**When should you use Fusion?** Fusion becomes valuable when your API spans multiple domains (products, reviews, accounts, shipping), when different teams own different parts of the API, or when you need to deploy and scale parts of your API independently. If you find yourself wanting to break a growing monolith into smaller, team-owned services without forcing clients to stitch together calls to multiple endpoints, Fusion is the tool for the job.
-## Thinking in Composed Graphs
+## Thinking in Composite Schemas
When you first encounter distributed GraphQL, there is a natural assumption about how subgraphs interact that turns out to be wrong. Getting the right mental model now will save you from the most common design mistakes.
-### One Graph, Many Contributors
+### One GraphQL Schema, Many Contributors
-In Fusion, all subgraphs contribute to **one shared graph**. There is no "Products API" and "Reviews API" that exist as separate GraphQL schemas -- there is one graph that describes your entire domain, and different subgraphs are responsible for different parts of it.
+In Fusion, all subgraphs contribute to **one shared GraphQL schema**. There is no "Products Schema" and "Reviews Schema" that exist as separate GraphQL schemas. There is one graph that describes your entire domain, and different subgraphs are responsible for different parts of it.
This means a single type can have fields contributed by multiple subgraphs. For example, the `Product` type might get its `name` and `price` fields from the Products subgraph, its `reviews` field from the Reviews subgraph, and its `deliveryEstimate` field from the Shipping subgraph. From the client's perspective, `Product` is one type with all those fields. The gateway figures out where each field lives and fetches it from the right place.
@@ -34,46 +47,61 @@ This seems reasonable, but it creates a problem: your subgraphs become coupled.
**How Fusion actually works:**
-> "My Users subgraph knows that each User has a `tenantId`, and I declare that `user.tenant` returns a `Tenant` identified by that ID. I don't need to know _how_ or _where_ the Tenant gets resolved -- the gateway handles that."
+> "My Users subgraph knows that each User has a `tenantId`, and I declare that `user.tenant` returns a `Tenant` identified by that ID. I don't need to know _how_ or _where_ the Tenant gets resolved, because the gateway handles that."
-In Fusion, your subgraph never calls another subgraph. Instead, it says: "this field returns a Tenant with this ID" and trusts the gateway to figure out the rest. The gateway knows which subgraph can resolve a Tenant by ID (using a **lookup** -- a query field that resolves an entity by its key). The Tenants subgraph provides this lookup resolver, and the gateway calls it when it needs to turn a tenant ID into a full Tenant object. The Users subgraph never talks to the Tenants subgraph directly -- the gateway handles all coordination.
+In Fusion, your subgraph never calls another subgraph. Instead, it says: "this field returns a Tenant with this ID" and trusts the gateway to figure out the rest. The gateway knows which subgraph can resolve a Tenant by ID, using a **lookup**, which is a query field that resolves an entity by its key. The Tenants subgraph provides this lookup resolver, and the gateway calls it when it needs to turn a tenant ID into a full Tenant object. The Users subgraph never talks to the Tenants subgraph directly. The gateway handles all coordination.
### Why This Matters
This design has practical consequences:
- **Subgraphs stay independent.** The Users subgraph does not import anything from the Tenants subgraph. It just knows that `Tenant` is an entity with an `id` field.
-- **Adding new subgraphs is safe.** If a new Billing subgraph wants to add a `billingPlan` field to the `Tenant` type, it can -- without modifying the Tenants or Users subgraphs.
-- **The gateway is the coordinator.** Cross-subgraph data fetching is the gateway's job, not yours. Your subgraph only needs to know how to resolve the fields it owns and how to look up its own entities by key.
-
-When you build your subgraphs in the following sections, keep this model in mind: each subgraph contributes to one graph, declares its entities and lookups, and trusts the gateway to wire everything together.
+- **Adding new subgraphs is safe.** If a new Billing subgraph wants to contribute a `billingPlan` field to the `Tenant` type, it can do so without modifying the Tenants or Users subgraphs.
+- **The gateway is the coordinator.** Cross-subgraph data fetching is the gateway's job, not yours. Your subgraph only needs to know how to resolve the fields it owns and how to look up its own entities by key. When you build your subgraphs in the following sections, keep this model in mind: each subgraph contributes to one API, declares its entities and lookups, and trusts the gateway to wire everything together.
## Key Concepts
Before diving into code, here are the core terms you will encounter throughout this guide. Each builds on the mental model from the previous section.
-**Subgraph** -- An independent GraphQL service that contributes types and fields to the overall graph. Each subgraph runs in its own process, owns its own data, and defines resolvers for the fields it contributes. In HotChocolate, a subgraph is a standard HotChocolate server with a few additional attributes that tell Fusion how its types fit into the larger graph.
+**Subgraph:** An independent GraphQL service that contributes types and fields to the overall API. Each subgraph runs in its own process, owns its own data, and defines resolvers for the fields it contributes. In Hot Chocolate, a subgraph is a standard Hot Chocolate server with a few additional attributes that tell Fusion how its types fit into the larger API.
-**Source Schema** -- The GraphQL schema exposed by a single subgraph. When you export a subgraph's schema (as a `.graphqls` file), that exported schema is the source schema. Fusion's composition engine reads source schemas from all your subgraphs and merges them into the composite schema.
+**Source Schema:** The GraphQL schema exposed by a single subgraph. When you export a subgraph's schema (as a `.graphqls` file), that exported schema is the source schema. Fusion's composition engine reads source schemas from all your subgraphs and merges them into the composite schema.
-**Composite Schema** -- The unified, client-facing GraphQL schema produced by merging all source schemas. All types and fields from every subgraph appear in the composite schema, and clients can query across subgraph boundaries in a single request -- as if they were querying a single monolithic GraphQL server. They never interact with individual subgraphs directly.
+**Composite Schema:** The unified, client-facing GraphQL schema produced by merging all source schemas. All types and fields from every subgraph appear in the composite schema, and clients can query across subgraph boundaries in a single request as if they were querying a single monolithic GraphQL server. They never interact with individual subgraphs directly.
-**Gateway** -- The service that sits between clients and subgraphs. It exposes the composite schema, receives client queries, analyzes each query, routes parts of it to the appropriate subgraphs, and combines the results into a single response. In HotChocolate, you create a gateway by calling `AddGraphQLGateway()` in a standard ASP.NET Core project.
+**Gateway:** The service that sits between clients and subgraphs. It exposes the composite schema, receives client queries, analyzes each query, routes parts of it to the appropriate subgraphs, and combines the results into a single response. In Hot Chocolate, you create a gateway by calling `AddGraphQLGateway()` in a standard ASP.NET Core project.
-**Query Planning** -- The process the gateway uses to execute a client query across multiple subgraphs. When a query touches fields from different subgraphs, the gateway analyzes the query, determines which subgraphs own which fields, and creates an execution plan that fetches data in the right order. For example, if a query asks for `product.reviews.author.name`, the gateway must first fetch the Product (from the Products subgraph), then fetch Reviews (to get author IDs), then fetch Users (to get names) -- coordinating across three subgraphs in sequence. This happens automatically -- you do not write query plans yourself.
+**Query Planning:** The process the gateway uses to build an optimized execution plan for a client query across multiple subgraphs. When a query touches fields from different subgraphs, the gateway analyzes the query, determines which subgraphs own which fields, and creates a plan that fetches data in the right order.
-**Entity** -- A type that can be uniquely identified and resolved across subgraphs. For example, `Product` is an entity because the Products subgraph defines it, the Reviews subgraph extends it with a `reviews` field, and the gateway can resolve a `Product` in any subgraph using its key (like `id`). Entities are defined by their **key fields** (like `id` or `sku`) that uniquely identify each instance. The gateway uses these keys to resolve entity references across subgraphs, which is what makes cross-subgraph types possible.
+**Entity:** A type with a stable key that can be referenced across subgraphs. Entity keys (such as `id` or `sku`) uniquely identify each instance and are used for cross-subgraph references and resolution.
-**Shareable** -- A marker that allows multiple subgraphs to define the same field on the same type. By default, Fusion requires that each non-key field belongs to exactly one subgraph -- if two subgraphs define the same field, composition fails with an error. When you mark a field with `[Shareable]`, you are telling Fusion: "this field is intentionally defined in multiple subgraphs, and all definitions return the same data." The gateway can then resolve the field from whichever subgraph is most convenient for a given query.
+**Lookup:** A query field the gateway uses to enrich an already-referenced entity with additional fields from another subgraph.
-**Lookup** -- A query field in a subgraph that resolves an entity by its key. When the gateway needs to fetch a `Product` from the Products subgraph, it calls that subgraph's lookup field (e.g., `productById(id: 1)`). In HotChocolate, you mark a resolver as a lookup by adding the `[Lookup]` attribute. Every entity needs at least one lookup so the gateway can resolve references to it. Lookups come in two flavors:
+```graphql
+type Query {
+ productById(id: ID!): Product @lookup
+}
+```
-- A **public lookup** (without `[Internal]`) serves two purposes: clients can call it directly as a query field, and the gateway uses it for entity resolution. Because clients can call it, a public lookup should validate that the entity exists and return `null` if it does not.
-- An **internal lookup** (marked with `[Internal]`) is hidden from the composite schema and is only used by the gateway during query planning. Internal lookups often just construct a stub object from an ID without checking whether the entity actually exists -- this is fine because the gateway only calls them as part of entity resolution, where the entity's existence has already been established by another subgraph.
+- An **internal lookup** (marked with the `@internal` directive) is excluded from the composite schema, cannot be called by clients, and is used only by the gateway for cross-subgraph entity resolution.
+
+```graphql
+type Query {
+ productById(id: ID!): Product @internal @lookup
+}
+```
+
+**Shareable:** A contract for fields that are defined in multiple subgraphs. By default, each non-key field must be defined by exactly one subgraph. If the same field appears in multiple subgraphs, composition fails until every definition is explicitly marked with the `@sharable` directive. This asserts that all definitions have the same semantics, and it allows the query planner to fetch the field from any subgraph subgraph providing this field.
+
+```graphql
+type User {
+ name: String @sharable
+}
+```
## Prerequisites
-To follow this guide, you need the following installed on your machine.
+To follow this tutorial, you need the following installed on your machine.
### .NET SDK
@@ -90,7 +118,7 @@ You should see `10.0.100` or higher.
The Nitro CLI is a .NET tool that handles schema composition. Install it globally:
```bash
-dotnet tool install -g ChilliCream.Nitro.CLI
+dotnet tool install -g ChilliCream.Nitro.CommandLine --version "16.0.0-p.11.35"
```
Verify the installation:
@@ -99,54 +127,31 @@ Verify the installation:
nitro version
```
-### NuGet Packages
-
-You do not need to install packages manually -- each project you create will reference them in its `.csproj` file. For reference, these are the HotChocolate packages used in this guide:
+### Hot Chocolate Templates
-**For subgraphs:**
+Install the Hot Chocolate templates at the same preview version used in this guide:
-- `HotChocolate.AspNetCore` -- The HotChocolate GraphQL server for ASP.NET Core
-- `HotChocolate.Types.Analyzers` -- Source generator that auto-registers your types
-
-**For the gateway:**
-
-- `HotChocolate.Fusion.AspNetCore` -- The Fusion gateway for ASP.NET Core
-
-All packages use prerelease version `16.0.0-p.11.2` or later, available on nuget.org. At the time of writing, the latest preview is `16.0.0-p.11.2`.
-
-### What You Do Not Need
-
-This guide deliberately keeps infrastructure simple so you can focus on Fusion concepts. You do **not** need:
-
-- A database -- all data is in-memory
-- Docker or containers
-- .NET Aspire
-- Authentication or Keycloak
-- A Nitro cloud account
-
-Everything runs locally on your machine.
+```bash
+dotnet new install HotChocolate.Templates@16.0.0-p.11.35
+```
## Create Your First Subgraph (Products)
-Time to write code. You will create a Products subgraph that exposes a few products through a GraphQL API. This subgraph will later become part of your composed graph.
+Time to write code. You will create a Products subgraph that exposes a few products through a GraphQL API. This subgraph will later become part of your composed API.
### Project Setup
-Create a new ASP.NET Core web project and add the required packages:
+Create a new GraphQL server project from the template:
```bash
mkdir fusion-getting-started
cd fusion-getting-started
-dotnet new web -n Products
-cd Products
-
-dotnet add package HotChocolate.AspNetCore --version "16.0.0-p.11.2"
-dotnet add package HotChocolate.Types.Analyzers --version "16.0.0-p.11.2"
-
-cd ..
+dotnet new graphql -n Products
```
+The template creates sample files in `Products/Types` (`Author.cs`, `Book.cs`, `Query.cs`). Delete those files before continuing so your schema only contains the tutorial types.
+
Your `Products/Products.csproj` should now look like this:
```xml
@@ -156,13 +161,19 @@ Your `Products/Products.csproj` should now look like this:
net10.0
enable
enable
+ preview
+
+
+
+
-
-
- all
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
@@ -171,9 +182,10 @@ Your `Products/Products.csproj` should now look like this:
### Configure the Port
-By default, ASP.NET Core picks a port for you. To keep things predictable, configure the Products subgraph to run on port 5001. Edit `Products/Properties/launchSettings.json` and set the `applicationUrl` under the `http` profile to:
+The `dotnet new graphql` template already defines a default port in `launchSettings.json`. For this tutorial, change it so the Products subgraph runs on port 5001. Edit `Products/Properties/launchSettings.json` and set `launchUrl` and `applicationUrl` under the `http` profile to:
```json
+"launchUrl": "http://localhost:5001/graphql",
"applicationUrl": "http://localhost:5001"
```
@@ -196,7 +208,7 @@ public class Product
}
```
-This is a plain C# class -- nothing GraphQL-specific yet.
+This is a plain C# class with nothing GraphQL-specific yet.
### Add In-Memory Data
@@ -222,7 +234,7 @@ public static class ProductRepository
}
```
-In production, you would use `[DataLoader]` to batch database lookups -- this prevents N+1 queries when the gateway resolves entities across subgraphs. See the fusion-demo repository for DataLoader examples.
+> In production, use **DataLoader** or **batch resolvers** in your subgraphs to batch backend lookups and reduce N+1 query patterns. This is especially important for entity lookups across subgraphs. See the [Fusion demo project](https://github.com/ChilliCream/fusion-demo) for a more complete example.
### Expose Products Through GraphQL
@@ -243,22 +255,20 @@ public static partial class ProductQueries
}
```
-The `partial` keyword is required because the source generator adds generated code to this class at compile time.
-
Two attributes to notice:
-- **`[QueryType]`** tells HotChocolate that this class contributes fields to the `Query` root type. The source generator (from `HotChocolate.Types.Analyzers`) automatically registers these fields.
-- **`[Lookup]`** marks `GetProductById` as a lookup resolver. This is how the gateway resolves a `Product` when another subgraph references it. Without this attribute, the gateway would have no way to fetch a Product by its ID from this subgraph. Because this lookup is **public** (not marked `[Internal]`), it also appears in the composite schema as a query field clients can call directly. Notice it returns `Product?` (nullable) -- if the ID does not match any product, it returns `null`. This is important for public lookups because clients can call them with arbitrary IDs.
+- **`[QueryType]`** tells Hot Chocolate that this class contributes fields to the `Query` root type. The source generator (from `HotChocolate.Types.Analyzers`) automatically registers these fields.
+- **`[Lookup]`** marks `GetProductById` as a lookup resolver. This is how the gateway resolves a `Product` when another subgraph references it. Without this attribute, the gateway would have no way to fetch a Product by its ID from this subgraph. Because this lookup is **public**, it also appears in the composite schema as a query field clients can call directly. Lookups must return nullable entity types (`Product?`) so unresolved keys can return `null` and avoid cascading failures when one or more subgraphs cannot provide requested fields for an entity.
### Configure the Server
-Replace the contents of `Program.cs` with:
+Set `Program.cs` to:
```csharp
var builder = WebApplication.CreateBuilder(args);
-builder.Services
- .AddGraphQLServer()
+builder
+ .AddGraphQL("Products")
.AddTypes();
var app = builder.Build();
@@ -267,7 +277,7 @@ app.MapGraphQL();
app.RunWithGraphQLCommands(args);
```
-This is a minimal HotChocolate server. `AddTypes()` is a source-generated method that automatically registers all types discovered by the `HotChocolate.Types.Analyzers` package -- including `ProductQueries` and the `Product` type. The `RunWithGraphQLCommands(args)` method (instead of `app.Run()`) enables CLI commands, including schema export which you will use shortly.
+This is a minimal Hot Chocolate server. `AddGraphQL("Products")` sets the subgraph name used during schema export. `AddTypes()` registers all discovered types, including `ProductQueries` and `Product`. `RunWithGraphQLCommands(args)` enables CLI commands, including schema export.
### Test the Subgraph
@@ -320,29 +330,22 @@ Stop the server with `Ctrl+C` before continuing.
### Export the Schema
-The gateway needs each subgraph's schema for composition. HotChocolate can export the schema files automatically using a CLI command. Add the command-line package:
-
-```bash
-cd Products
-dotnet add package HotChocolate.AspNetCore.CommandLine --version "16.0.0-p.11.2"
-```
-
-Now export the schema. From the `fusion-getting-started` directory:
+The gateway needs each subgraph's source schema for composition. Hot Chocolate can export source schema files automatically. From the `fusion-getting-started` directory, run:
```bash
-dotnet run ./Products -- schema export
+dotnet run --project ./Products -- schema export
```
This generates two files in the Products project directory:
-- **`schema.graphqls`** -- The Products subgraph's GraphQL schema, describing its types and fields.
-- **`schema-settings.json`** -- A companion file that tells Fusion the subgraph's name and runtime URL.
+- **`schema.graphqls`**: The Products subgraph's source schema, describing its types and fields.
+- **`schema-settings.json`**: A companion file that tells Fusion the subgraph's name and runtime URL and other subgraph options.
-Open `Products/schema-settings.json` and verify it contains the subgraph name. You will need to set the transport URL so the gateway knows where to reach this subgraph at runtime. Update it to:
+Because `Program.cs` uses `AddGraphQL("Products")`, `Products/schema-settings.json` already contains `"name": "Products"`. Update the transport URL to match port 5001:
```json
{
- "name": "products",
+ "name": "Products",
"transports": {
"http": {
"url": "http://localhost:5001/graphql"
@@ -351,7 +354,7 @@ Open `Products/schema-settings.json` and verify it contains the subgraph name. Y
}
```
-The `name` field is the unique identifier for this subgraph in the composed graph. The `url` is where the gateway will send requests to this subgraph at runtime.
+The `name` field identifies this subgraph within the composite schema and must be unique. The `url` is where the gateway sends requests to this subgraph at runtime.
### What You Built
@@ -362,32 +365,28 @@ Your Products subgraph now:
- Has an exported schema file (`schema.graphqls`) describing its types
- Has a settings file (`schema-settings.json`) telling Fusion where to reach it at runtime
-In the next section, you will create a second subgraph (Reviews) that references Products -- this is where the power of Fusion starts to show.
+In the next section, you will create a second subgraph (Reviews) that references Products. This is where the power of Fusion starts to show.
## Create a Second Subgraph (Reviews)
-Now you will create a Reviews subgraph that adds review data to the graph. The key part: the Reviews subgraph will add a `reviews` field to the `Product` type that lives in the Products subgraph. This is Fusion's core capability -- multiple subgraphs contributing fields to the same type.
+Now you will create a Reviews subgraph that adds review data to the API. The key part is that the Reviews subgraph will add a `reviews` field to the `Product` type that lives in the Products subgraph. This is Fusion's core capability: multiple subgraphs contributing fields to the same type.
### Project Setup
From the `fusion-getting-started` directory:
```bash
-dotnet new web -n Reviews
-cd Reviews
-
-dotnet add package HotChocolate.AspNetCore --version "16.0.0-p.11.2"
-dotnet add package HotChocolate.AspNetCore.CommandLine --version "16.0.0-p.11.2"
-dotnet add package HotChocolate.Types.Analyzers --version "16.0.0-p.11.2"
-
-cd ..
+dotnet new graphql -n Reviews
```
+The template also creates sample files in `Reviews/Types` (`Author.cs`, `Book.cs`, `Query.cs`). Delete those files before continuing.
+
### Configure the Port
-Edit `Reviews/Properties/launchSettings.json` and set the `applicationUrl` under the `http` profile to:
+The `dotnet new graphql` template already defines a default port in `launchSettings.json`. For this tutorial, change it so the Reviews subgraph runs on port 5002. Edit `Reviews/Properties/launchSettings.json` and set `launchUrl` and `applicationUrl` under the `http` profile to:
```json
+"launchUrl": "http://localhost:5002/graphql",
"applicationUrl": "http://localhost:5002"
```
@@ -458,9 +457,9 @@ public sealed record Product(int Id)
}
```
-This is **not** a duplicate of the Product type from the Products subgraph. It is an **entity stub** -- a lightweight declaration that says: "I know `Product` exists in the graph, identified by `Id`, and I want to add a `reviews` field to it."
+This is **not** a duplicate of the Product type from the Products subgraph. It is an **entity stub**, a lightweight declaration that says: "I know `Product` exists in the API, identified by `Id`, and I want to add a `reviews` field to it."
-The Reviews subgraph does not define `name`, `price`, or any other Product fields. It only contributes the `reviews` field. When the gateway composes the graph, it merges this stub with the full `Product` type from the Products subgraph. Clients see one `Product` type with:
+The Reviews subgraph does not define `name`, `price`, or any other Product fields. It only contributes the `reviews` field. When the gateway composes the API, it merges this stub with the full `Product` type from the Products subgraph. Clients see one `Product` type with:
- `id`, `name`, and `price` from the Products subgraph
- `reviews` from the Reviews subgraph
@@ -486,7 +485,7 @@ public static partial class ReviewQueries
}
```
-And create `ProductQueries.cs` -- this provides the internal lookup that lets the gateway resolve Product references within the Reviews subgraph:
+And create `ProductQueries.cs`. This provides the internal lookup that lets the gateway resolve Product references within the Reviews subgraph:
```csharp
namespace Reviews;
@@ -495,7 +494,7 @@ namespace Reviews;
public static partial class ProductQueries
{
[Lookup, Internal]
- public static Product GetProductById(int id)
+ public static Product? GetProductById(int id)
=> new(id);
}
```
@@ -503,21 +502,21 @@ public static partial class ProductQueries
Two attributes to notice:
- **`[Lookup]`** makes this a lookup resolver for the `Product` entity. The gateway calls this when it needs to resolve a Product reference.
-- **`[Internal]`** hides this field from the composite schema. Clients cannot call `productById` on the Reviews subgraph directly -- it exists only for the gateway's internal use during query planning.
+- **`[Internal]`** hides this field from the composite schema. Clients cannot call `productById` on the Reviews subgraph directly. It exists only for the gateway's internal use during query planning.
-Why is the internal lookup needed? When a client queries `review.product.reviews`, the gateway needs a way to enter the Reviews subgraph's `Product` type so it can resolve the `reviews` field. The internal lookup provides this entry point -- given a product ID (which the gateway already knows from another subgraph), it constructs a `Product` stub that the `reviews` field can then resolve against.
+Why is the internal lookup needed? When a client queries `review.product.reviews`, the gateway needs a way to enter the Reviews subgraph's `Product` type so it can resolve the `reviews` field. The internal lookup provides this entry point. Given a product ID (which the gateway already knows from another subgraph), it constructs a `Product` stub that the `reviews` field can then resolve against.
-Notice that this lookup returns `Product` (non-nullable) and does `new(id)` -- it just constructs a stub from the ID without checking whether that product actually exists. This is safe because internal lookups are never called by clients directly. The gateway only calls them during entity resolution, after another subgraph has already confirmed the entity exists. Compare this to the Products subgraph's public `GetProductById`, which calls `ProductRepository.GetById(id)` and returns `Product?` (nullable) because clients can call it with any ID.
+Notice that this lookup returns `Product?` (nullable), even though it is internal. Lookups should be nullable so unresolved keys can return `null` and avoid cascading failures across subgraphs. In this sample, the resolver still constructs a stub with `new(id)` because the key is already known.
### Configure the Server
-Replace the contents of `Program.cs` with:
+Set `Program.cs` to:
```csharp
var builder = WebApplication.CreateBuilder(args);
-builder.Services
- .AddGraphQLServer()
+builder
+ .AddGraphQL("Reviews")
.AddTypes();
var app = builder.Build();
@@ -526,11 +525,11 @@ app.MapGraphQL();
app.RunWithGraphQLCommands(args);
```
-This is identical to the Products subgraph's `Program.cs`.
+This follows the same pattern as the Products subgraph, but sets the subgraph name to `Reviews`.
### Add the ObjectType Extension
-There is one more piece needed. The `Product` record defines the `reviews` field, but HotChocolate needs to know that this is an **extension** of the existing `Product` type, not a new type. Create `ProductNode.cs`:
+There is one more piece needed. The `Product` record defines the `reviews` field, but Hot Chocolate needs to know that this is an **extension** of the existing `Product` type, not a new type. Create `ProductNode.cs`:
```csharp
using HotChocolate.Types;
@@ -546,11 +545,11 @@ public static partial class ProductNode
}
```
-- **`[ObjectType]`** tells HotChocolate that this class extends the `Product` type. Fields defined on the `Product` record (like `GetReviews()`) become part of the `Product` type in GraphQL.
+- **`[ObjectType]`** tells Hot Chocolate that this class extends the `Product` type. Fields defined on the `Product` record (like `GetReviews()`) become part of the `Product` type in GraphQL.
- **`[BindMember(nameof(Review.ProductId))]`** replaces the raw `ProductId` integer on `Review` with a resolved `Product` object. In the exported schema, clients see `review.product` (returning a full `Product`) instead of `review.productId` (returning a raw integer).
-- **`[Parent]`** tells HotChocolate to inject the parent object (the `Review`) into the resolver. This is how `GetProduct()` accesses the `ProductId` from the review it belongs to.
+- **`[Parent]`** tells Hot Chocolate to inject the parent object (the `Review`) into the resolver. This is how `GetProduct()` accesses the `ProductId` from the review it belongs to.
-This transformation happens in the GraphQL schema, not your C# classes. Your `Review` class still has a `ProductId` field internally, but in the exported schema that field is replaced with `product: Product`. When a client queries `review.product`, HotChocolate calls `GetProduct()`, reads the `ProductId` from the parent Review, and returns a `Product` stub with just that ID. The gateway then uses the Product lookup to fetch the full product data from whichever subgraph owns it.
+This transformation happens in the GraphQL schema, not your C# classes. Your `Review` class still has a `ProductId` field internally, but in the exported schema that field is replaced with `product: Product`. When a client queries `review.product`, Hot Chocolate calls `GetProduct()`, reads the `ProductId` from the parent Review, and returns a `Product` stub with just that ID. The gateway then uses the Product lookup to fetch the full product data from whichever subgraph owns it.
### Test the Subgraph
@@ -605,23 +604,28 @@ query {
}
```
-You should see each review with its product reference, and each product with its reviews. Notice that `product.name` and `product.price` are not available here -- those fields live in the Products subgraph and will only appear after composition in the gateway. Within the Reviews subgraph alone, `Product` only has the fields the Reviews subgraph contributes: `id` and `reviews`.
+You should see each review with its product reference, and each product with its reviews. Notice that `product.name` and `product.price` are not available here. Those fields live in the Products subgraph and will only appear after composition in the gateway. Within the Reviews subgraph alone, `Product` only has the fields the Reviews subgraph contributes: `id` and `reviews`.
Stop the server with `Ctrl+C`.
### Export the Schema
-Export the Reviews subgraph's schema. From the `fusion-getting-started` directory:
+Export the Reviews subgraph's source schema. From the `fusion-getting-started` directory:
```bash
-dotnet run ./Reviews -- schema export
+dotnet run --project ./Reviews -- schema export
```
-This generates `schema.graphqls` and `schema-settings.json` in the Reviews project directory. Open `Reviews/schema-settings.json` and update the transport URL:
+This generates two files in the Reviews project directory:
+
+- **`schema.graphqls`**: The Reviews subgraph's source schema, describing its types and fields.
+- **`schema-settings.json`**: A companion file that tells Fusion the subgraph's name and runtime URL and other subgraph options.
+
+Because `Program.cs` uses `AddGraphQL("Reviews")`, `Reviews/schema-settings.json` already contains `"name": "Reviews"`. Update the transport URL to match port 5002:
```json
{
- "name": "reviews",
+ "name": "Reviews",
"transports": {
"http": {
"url": "http://localhost:5002/graphql"
@@ -630,21 +634,23 @@ This generates `schema.graphqls` and `schema-settings.json` in the Reviews proje
}
```
+The `name` field identifies this subgraph within the composite schema and must be unique. The `url` is where the gateway sends requests to this subgraph at runtime.
+
### What You Built
Your Reviews subgraph now:
- Exposes a `reviews` query and a `reviewById` lookup
-- Adds a `reviews` field to the `Product` type using an entity stub -- without duplicating any Product data
+- Adds a `reviews` field to the `Product` type using an entity stub, without duplicating any Product data
- Replaces the raw `productId` on reviews with a resolved `Product` reference via `[BindMember]`
- Provides an internal `productById` lookup that the gateway uses for cross-subgraph resolution
- Has an exported schema and settings file ready for composition
-You now have two independent subgraphs that contribute to one shared graph. In the next section, you will compose them into a single composite schema.
+You now have two independent subgraphs that contribute to one shared API. In the next section, you will compose them into a single composite schema.
## Compose the Schemas with Nitro CLI
-You now have two subgraphs, each with an exported schema and a settings file. The next step is **composition** -- merging these source schemas into a single composite schema that the gateway will serve to clients.
+You now have two subgraphs, each with an exported schema and a settings file. The next step is **composition**: merging these source schemas into a single composite schema that the gateway will serve to clients.
Composition is handled by the Nitro CLI. It reads each subgraph's `.graphqls` schema file and its companion `-settings.json` file, validates that the schemas are compatible, and produces a **Fusion archive** (`.far` file) that contains everything the gateway needs: the composite schema, the execution schema with routing metadata, and the transport configuration for each subgraph.
@@ -682,16 +688,8 @@ Each subgraph directory has a `schema.graphqls` and a `schema-settings.json`. Th
From the `fusion-getting-started` directory, run:
```bash
-nitro fusion compose \
- --source-schema-file Products/schema.graphqls \
- --source-schema-file Reviews/schema.graphqls \
- --archive gateway.far
-```
-
-On Windows (PowerShell or cmd), put everything on one line:
-
-```bash
-nitro fusion compose --source-schema-file Products/schema.graphqls --source-schema-file Reviews/schema.graphqls --archive gateway.far
+mkdir Gateway
+nitro fusion compose --s Products/schema.graphqls --s Reviews/schema.graphqls --f Gateway/gateway.far
```
If composition succeeds, you will see output similar to:
@@ -702,34 +700,16 @@ Merging schemas...
Fusion archive created: gateway.far
```
-The exact output may vary by Nitro CLI version, but you should see confirmation that the archive was created. The `gateway.far` file is a binary package containing the composed gateway configuration. You do not need to look inside this file -- the gateway reads it directly. You will pass it to the gateway in the next section.
-
-Notice that composition validates your schemas at build time. If there were conflicts between subgraphs -- say, two subgraphs defining the same field without `[Shareable]` -- you would find out now, not when a user hits a broken query path in production. This is one of Fusion's key advantages.
+The exact output may vary by Nitro CLI version. Confirm that `Gateway/gateway.far` was created. This file contains the composed gateway configuration, and the gateway loads it directly.
### What Happens During Composition
-The Nitro CLI performs three steps:
-
-1. **Validate** each source schema individually -- are they valid GraphQL? Are the Fusion attributes used correctly?
-2. **Merge** the source schemas -- combine types with the same name, verify that non-shared fields appear in only one subgraph, and resolve entity references.
-3. **Produce** the composite schema -- the unified schema clients will query, plus the internal routing metadata the gateway needs to plan queries.
-
-In this case, composition merges the `Product` type from the Products subgraph (with `id`, `name`, `price`) and the `Product` entity stub from the Reviews subgraph (with `reviews`). The result is one `Product` type with all four fields.
-
-Lookup resolvers are also merged into the gateway configuration. The gateway knows it can resolve a Product by ID from the Products subgraph (for full product data) or from the Reviews subgraph (for the entity stub). The internal lookup in Reviews (marked `[Internal]`) is hidden from the composite schema but available to the gateway for query planning.
-
-### Troubleshooting Composition Errors
-
-If composition fails, the CLI reports specific errors. Common issues include:
+The Nitro CLI performs four steps:
-- **"Field X is defined in multiple subgraphs without `[Shareable]`"** -- Two subgraphs define the same field on the same type. Either mark the field as `[Shareable]` in both subgraphs (if they return the same data), or remove the duplicate.
-- **"No lookup found for entity X"** -- A subgraph references an entity type but no subgraph provides a lookup for it. Add a `[Lookup]` resolver.
-- **"Entity X has no key fields"** -- An entity type is referenced across subgraphs but does not have a key field. Make sure your entity stub and main entity definition both have the same key field (usually `Id`).
-- **"Schema file not found" or "settings file not found"** -- The `.graphqls` file does not have a matching `-settings.json` file in the same directory. Make sure they share the same prefix (e.g., `schema.graphqls` and `schema-settings.json`).
-
-If you see errors, check that you exported the schemas after your latest code changes (`dotnet run ./ -- schema export` from the solution root).
-
-With composition complete, you now have a `gateway.far` file containing your composed graph. In the next section, you will create a gateway that loads this file and serves the unified schema to clients.
+1. **Validate** source schemas: check GraphQL validity and Fusion metadata.
+2. **Merge** source schemas: combine matching types and enforce field ownership rules.
+3. **Check satisfiability**: verify that cross-subgraph selections can actually be fulfilled with the available keys and lookups.
+4. **Produce** output: create the composite schema plus routing metadata for the gateway.
## Run the Fusion Gateway
@@ -740,14 +720,13 @@ The gateway is the service that clients connect to. It loads the composed config
From the `fusion-getting-started` directory:
```bash
-dotnet new web -n Gateway
cd Gateway
-
-dotnet add package HotChocolate.Fusion.AspNetCore --version "16.0.0-p.11.2"
-
+dotnet new graphql-gateway
cd ..
```
+The `graphql-gateway` template already includes the `HotChocolate.Fusion.AspNetCore` package and a working gateway `Program.cs`.
+
Your `Gateway/Gateway.csproj` should look like this:
```xml
@@ -760,7 +739,7 @@ Your `Gateway/Gateway.csproj` should look like this:
-
+
@@ -768,31 +747,26 @@ Your `Gateway/Gateway.csproj` should look like this:
### Configure the Port
-Edit `Gateway/Properties/launchSettings.json` and set the `applicationUrl` under the `http` profile to:
+Edit `Gateway/Properties/launchSettings.json` and set `launchUrl` and `applicationUrl` under the `http` profile to:
```json
+"launchUrl": "http://localhost:5000/graphql",
"applicationUrl": "http://localhost:5000"
```
The gateway runs on port 5000. The subgraphs run on ports 5001 (Products) and 5002 (Reviews).
-### Copy the Fusion Archive
-
-Copy the `gateway.far` file you created during composition into the Gateway project directory:
-
-```bash
-cp gateway.far Gateway/gateway.far
-```
+### Verify the Fusion Archive
-On Windows:
+The composition step already wrote `gateway.far` into the `Gateway` directory (`--f Gateway/gateway.far`). Verify that the file exists before continuing:
```bash
-copy gateway.far Gateway\gateway.far
+ls Gateway/gateway.far
```
### Configure the Gateway
-Replace the contents of `Gateway/Program.cs` with:
+The template generates `Gateway/Program.cs`. It should look like this:
```csharp
var builder = WebApplication.CreateBuilder(args);
@@ -822,21 +796,21 @@ You need all three services running at the same time: both subgraphs and the gat
**Important:** Start the Products and Reviews subgraphs before the gateway. The gateway connects to each subgraph on startup and may log errors if they are not reachable.
-**Terminal 1 -- Products subgraph:**
+**Terminal 1: Products subgraph**
```bash
cd fusion-getting-started/Products
dotnet run
```
-**Terminal 2 -- Reviews subgraph:**
+**Terminal 2: Reviews subgraph**
```bash
cd fusion-getting-started/Reviews
dotnet run
```
-**Terminal 3 -- Gateway:**
+**Terminal 3: Gateway**
```bash
cd fusion-getting-started/Gateway
@@ -852,7 +826,7 @@ info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
```
-The port number will differ for each service (5001, 5002, and 5000). If you see errors like "Address already in use," another process is using that port -- either stop it or choose a different port in `launchSettings.json`.
+The port number will differ for each service (5001, 5002, and 5000). If you see errors like "Address already in use," another process is using that port. Either stop it or choose a different port in `launchSettings.json`.
### Verify the Gateway
@@ -868,7 +842,7 @@ query {
}
```
-You should see the same product data as when you queried the Products subgraph directly. The difference is that this query went through the gateway, which routed it to the Products subgraph behind the scenes. If you look at the terminal running the Products subgraph (Terminal 1), you should see a log entry showing it received and processed the request -- this confirms the gateway successfully routed the query.
+You should see the same product data as when you queried the Products subgraph directly. The difference is that this query went through the gateway, which routed it to the Products subgraph behind the scenes. If you look at the terminal running the Products subgraph (Terminal 1), you should see a log entry showing it received and processed the request. This confirms the gateway successfully routed the query.
### What You Built
@@ -877,7 +851,7 @@ Your gateway now:
- Loads the composed configuration from the `gateway.far` file
- Exposes the unified composite schema on port 5000
- Routes queries to the Products and Reviews subgraphs as needed
-- Acts as the single entry point for clients -- they never talk to the subgraphs directly
+- Acts as the single entry point for clients, so they never talk to the subgraphs directly
In the next section, you will run queries that demonstrate the gateway coordinating data across both subgraphs in a single request.
@@ -941,15 +915,15 @@ Look at what happened: `name` and `price` came from the Products subgraph, while
Behind the scenes, the gateway executed a query plan with multiple steps:
-1. **Fetched the products** from the Products subgraph -- this returned `id`, `name`, and `price` for each product.
-2. **Resolved the reviews** from the Reviews subgraph -- using each product's `id`, the gateway called the Reviews subgraph's internal `productById` lookup to get a `Product` stub, then resolved the `reviews` field on each stub.
+1. **Fetched the products** from the Products subgraph. This returned `id`, `name`, and `price` for each product.
+2. **Resolved the reviews** from the Reviews subgraph. Using each product's `id`, the gateway called the Reviews subgraph's internal `productById` lookup to get a `Product` stub, then resolved the `reviews` field on each stub.
3. **Combined the results** into a single response that looks exactly like it came from one GraphQL server.
The client never knew that two separate services were involved. This is the core promise of Fusion: multiple subgraphs, one unified API.
### Try Another Query
-You can also query in the other direction -- start from reviews and reach into product data:
+You can also query in the other direction: start from reviews and reach into product data.
```graphql
query {
@@ -999,9 +973,9 @@ This time, `body` and `stars` came from the Reviews subgraph, while `product.nam
### Compare: Before and After Composition
-Remember in the previous section, when you queried `review.product` on the Reviews subgraph directly? You could see `product.id` and `product.reviews`, but `product.name` and `product.price` were missing -- those fields did not exist in the Reviews subgraph.
+Remember in the previous section, when you queried `review.product` on the Reviews subgraph directly? You could see `product.id` and `product.reviews`, but `product.name` and `product.price` were missing because those fields did not exist in the Reviews subgraph.
-Now, through the gateway, `product.name` and `product.price` are available. Composition merged the Product type from both subgraphs, and the gateway resolves each field from the subgraph that owns it. This is what it means for subgraphs to contribute to one shared graph.
+Now, through the gateway, `product.name` and `product.price` are available. Composition merged the Product type from both subgraphs, and the gateway resolves each field from the subgraph that owns it. This is what it means for subgraphs to contribute to one shared API.
### Lookup a Single Product
@@ -1020,7 +994,7 @@ query {
}
```
-This uses the `productById` lookup from the Products subgraph -- the public one that appears in the composite schema. The gateway then fetches reviews from the Reviews subgraph using its internal lookup. Public lookups serve as both client-facing query fields and gateway entity resolution entry points, while internal lookups are only used by the gateway behind the scenes.
+This uses the `productById` lookup from the Products subgraph, the public one that appears in the composite schema. The gateway then fetches reviews from the Reviews subgraph using its internal lookup. Public lookups serve as both client-facing query fields and gateway entity resolution entry points, while internal lookups are only used by the gateway behind the scenes.
### What You Accomplished
@@ -1032,15 +1006,15 @@ You proved that Fusion works:
- Cross-subgraph entity resolution happens automatically through lookups
- The result is a unified API that feels like one service
-The Products and Reviews subgraphs are completely independent -- they do not import each other's code, do not call each other directly, and can be deployed and scaled separately. Yet clients can query across both as if they were one unified schema.
+The Products and Reviews subgraphs are completely independent. They do not import each other's code, do not call each other directly, and can be deployed and scaled separately. Yet clients can query across both as if they were one unified schema.
## Sharing Fields with `[Shareable]`
-In the tutorial so far, the Products and Reviews subgraphs each contribute **different** fields to the `Product` type -- Products owns `name` and `price`, while Reviews owns `reviews`. There is no overlap, so composition works without any special annotations.
+In the tutorial so far, the Products and Reviews subgraphs each contribute **different** fields to the `Product` type. Products owns `name` and `price`, while Reviews owns `reviews`. There is no overlap, so composition works without any special annotations.
But what happens when two subgraphs need to define the **same** field on the same type? By default, Fusion treats this as an error. If two subgraphs both define `Product.name`, composition fails because Fusion does not know which subgraph should be authoritative.
-The `[Shareable]` attribute solves this. When you mark a field as shareable in both subgraphs, you are telling Fusion: "these definitions are intentional and return the same data -- use whichever is most efficient."
+The `[Shareable]` attribute solves this. When you mark a field as shareable in both subgraphs, you are telling Fusion: "these definitions are intentional and return the same data. Use whichever is most efficient."
### When You Need It
@@ -1077,7 +1051,7 @@ Both subgraphs define `Product.name` and mark it `[Shareable]`. Composition succ
### The Rule
-Without `[Shareable]`, a non-key field must exist in exactly one subgraph. Key fields (like `id`) are automatically shareable -- you do not need the attribute on them. For any other field that appears in multiple subgraphs, add `[Shareable]` to **all** definitions of that field. If even one subgraph forgets to mark it, composition fails.
+Without `[Shareable]`, a non-key field must exist in exactly one subgraph. Key fields (like `id`) are automatically shareable, so you do not need the attribute on them. For any other field that appears in multiple subgraphs, add `[Shareable]` to **all** definitions of that field. If even one subgraph forgets to mark it, composition fails.
### When Not to Use It
@@ -1087,13 +1061,13 @@ If the fields return **different** data, they should have **different** names (e
## What's Next
-You now have a working Fusion setup: two subgraphs contributing to one composed graph, served through a single gateway. Here are some directions to explore next based on what you need:
+You now have a working Fusion setup: two subgraphs contributing to one composed API, served through a single gateway. Here are some directions to explore next based on what you need:
-- **I want to add another subgraph to this project** -- [Adding a Subgraph](/docs/fusion/v16/adding-a-subgraph)
-- **I want to understand entities more deeply** -- [Entities and Lookups](/docs/fusion/v16/entities-and-lookups)
-- **I need to deploy this** -- [Deployment & CI/CD](/docs/fusion/v16/deployment-and-ci-cd)
-- **I need to secure this** -- [Authentication and Authorization](/docs/fusion/v16/authentication-and-authorization)
-- **I'm coming from Apollo** -- [Coming from Apollo Federation](/docs/fusion/v16/coming-from-apollo-federation)
+- **I want to add another subgraph to this project**: [Adding a Subgraph](/docs/fusion/v16/adding-a-subgraph)
+- **I want to understand entities more deeply**: [Entities and Lookups](/docs/fusion/v16/entities-and-lookups)
+- **I need to deploy this**: [Deployment & CI/CD](/docs/fusion/v16/deployment-and-ci-cd)
+- **I need to secure this**: [Authentication and Authorization](/docs/fusion/v16/authentication-and-authorization)
+- **I'm coming from Apollo**: [Coming from Apollo Federation](/docs/fusion/v16/migration/coming-from-apollo-federation)
**Other useful resources:**
@@ -1109,7 +1083,7 @@ You now have a working Fusion setup: two subgraphs contributing to one composed
}
```
- When the argument name matches the entity field name (like `weight` above), HotChocolate infers the mapping automatically, so you can use `[Require]` without parameters. If the names differ, specify the entity field explicitly: `[Require("weight")] int productWeight`. The gateway resolves the required fields from their owning subgraph before calling your resolver. The required arguments are hidden from the composite schema -- clients never see them.
+ When the argument name matches the entity field name (like `weight` above), Hot Chocolate infers the mapping automatically, so you can use `[Require]` without parameters. If the names differ, specify the entity field explicitly: `[Require("weight")] int productWeight`. The gateway resolves the required fields from their owning subgraph before calling your resolver. The required arguments are hidden from the composite schema. Clients never see them.
- **You may see `[NodeResolver]` in demo code alongside `[Lookup]`.** This enables Relay-style global object identification (`node(id: ...)` queries). The fusion-demo uses it on every entity lookup. It is not required for basic Fusion setups.
diff --git a/website/src/docs/fusion/v16/index.md b/website/src/docs/fusion/v16/index.md
index 63fe2147871..2286ca6da7e 100644
--- a/website/src/docs/fusion/v16/index.md
+++ b/website/src/docs/fusion/v16/index.md
@@ -6,7 +6,7 @@ Fusion lets you split one GraphQL API into multiple smaller services, without ch
# What Is Fusion
-Fusion is ChilliCream's API gateway for exposing one GraphQL API over multiple upstream services. Those upstream services can be GraphQL, OpenAPI-based REST, or gRPC. Each service owns its contract and implementation. Fusion composes those contracts at build time, and the gateway orchestrates execution at runtime. Fusion implements the [GraphQL Composite Schemas specification (draft)](https://graphql.github.io/composite-schemas-spec/draft/), an open standard being developed under the GraphQL Foundation.
+Fusion is ChilliCream's API gateway for exposing one GraphQL API over multiple upstream services. Those upstream services can be GraphQL, OpenAPI-based REST, or gRPC. Each service owns its contract and implementation. Fusion composes those contracts at build time, and the gateway orchestrates execution at runtime. Fusion implements the [GraphQL Composite Schemas specification](https://graphql.github.io/composite-schemas-spec/draft/), an open standard being developed under the GraphQL Foundation.
The architecture has three parts:
@@ -16,10 +16,12 @@ The architecture has three parts:
A **source schema** is the contract document for a subgraph, such as a GraphQL schema, an OpenAPI document, or a gRPC/protobuf definition.
-**Composition** processes all source schemas and produces a Fusion archive (`.far`) that contains the composite schema and gateway configuration.
+**Composition** processes all source schemas, validates them against each other, and produces a Fusion archive (`.far`) that contains the composite schema and gateway configuration. Type conflicts, missing fields, and incompatible enums are caught in CI before deployment.
The **gateway** receives client requests, determines which subgraphs to call, executes those calls, and merges the results.
+**GraphQL subgraphs stay standard GraphQL servers.** The [GraphQL Composite Schemas specification](https://graphql.github.io/composite-schemas-spec/draft/) is designed so a standard GraphQL server can already act as a compatible subgraph. In a common Hot Chocolate setup, subgraphs remain normal Hot Chocolate servers with regular resolvers, without a separate distributed-runtime package or vendor-specific protocol layer.
+
The result: clients send one request to one endpoint and receive one unified response, while Fusion handles routing and aggregation across upstream services.
The following query touches three services, but the client doesn't know or care about this implementation detail.
@@ -43,37 +45,6 @@ query {
```
-## Three Things That Make Fusion Different
-
-A _lookup_ is a central concept in Fusion. It specifies how an entity can be resolved by a stable key. This concept also extends beyond federation and is useful in standard client-server communication.
-
-**Lookups use standard Query fields.** For GraphQL subgraphs, when the gateway needs to resolve an entity, it calls a normal Query field annotated with the `@lookup` directive. You can call the same field in tests, debug it with standard tools, and see exactly what it returns. There is no hidden internal protocol to implement, and the security model is the same as for any other GraphQL field.
-
-```graphql
-type Query {
- productById(id: ID!): Product @lookup
-}
-```
-
-If you are using Hot Chocolate, support for the composite schema specification is built in. Add the `[Lookup]` attribute to your resolver and you are ready to go.
-
-```csharp
-[QueryType]
-public static partial class ProductQueries
-{
- [Lookup]
- public static async Task GetProductById(
- [ID] int id,
- IProductByIdDataLoader productById,
- CancellationToken cancellationToken)
- => await productById.LoadAsync(id, cancellationToken);
-}
-```
-
-**Composition catches errors at build time.** When you run `nitro fusion compose`, the composition engine validates source schemas against each other. Type conflicts, missing fields, and incompatible enums are caught in CI before deployment.
-
-**No special runtime for GraphQL subgraphs.** The [GraphQL Composite Schemas specification](https://graphql.github.io/composite-schemas-spec/draft/) is designed so a standard GraphQL server can already act as a compatible subgraph. In a common Hot Chocolate setup, subgraphs remain normal Hot Chocolate servers with regular resolvers, without a separate distributed-runtime package or vendor-specific protocol layer.
-
# Key Terminology
| Term | Definition |
@@ -132,6 +103,6 @@ Where you go from here depends on what you need:
- **"I want to add another service to an existing project."** Go to [Adding a Subgraph](/docs/fusion/v16/adding-a-subgraph). It covers creating a new service (subgraph) that extends existing entity types.
-- **"I'm migrating from another distributed GraphQL framework."** Read [Coming from Apollo Federation](/docs/fusion/v16/coming-from-apollo-federation) or [Migrating from Schema Stitching](/docs/fusion/v16/migrating-from-schema-stitching). These guides map familiar concepts to Fusion equivalents and walk through a migration.
+- **"I'm migrating from another distributed GraphQL framework."** Read [Coming from Apollo Federation](/docs/fusion/v16/migration/coming-from-apollo-federation) or [Migrating from Schema Stitching](/docs/fusion/v16/migration/migrating-from-schema-stitching). These guides map familiar concepts to Fusion equivalents and walk through a migration.
- **"I need to deploy this."** See [Deployment & CI/CD](/docs/fusion/v16/deployment-and-ci-cd) for pipeline setup, schema management, and gateway configuration.
diff --git a/website/src/docs/fusion/v16/coming-from-apollo-federation.md b/website/src/docs/fusion/v16/migration/coming-from-apollo-federation.md
similarity index 100%
rename from website/src/docs/fusion/v16/coming-from-apollo-federation.md
rename to website/src/docs/fusion/v16/migration/coming-from-apollo-federation.md
diff --git a/website/src/docs/fusion/v16/migrating-from-schema-stitching.md b/website/src/docs/fusion/v16/migration/migrating-from-schema-stitching.md
similarity index 100%
rename from website/src/docs/fusion/v16/migrating-from-schema-stitching.md
rename to website/src/docs/fusion/v16/migration/migrating-from-schema-stitching.md