From a4093e5f3301da3f293e2fedd7ad552cd201bddd Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Tue, 9 Sep 2025 11:38:46 +0200 Subject: [PATCH 1/4] docs(blog): public graphql schema federation contracts --- .../page.mdx | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx diff --git a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx new file mode 100644 index 0000000000..f274ad1f49 --- /dev/null +++ b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx @@ -0,0 +1,152 @@ +--- +title: Incrementally Exposing a Public GraphQL API with Federation Contracts +authors: laurin +tags: [federation, graphql, hive] +date: 2025-09-09 +description: + 'Learn how to safely expose a public GraphQL API from a monolith using federation contracts and + tags, enabling incremental and controlled schema evolution.' +--- + +Many teams start with a **single GraphQL monolith** powering their applications. Over time, the need +arises to expose parts of that schema publicly - whether for partners, customers, or other external +integrations. + +But here’s the problem: + +Your internal schema wasn’t designed for public consumption. It likely contains inconsistent naming, +experimental fields, and sensitive operations you don’t want outsiders touching. + +At the same time, you don’t want to maintain multiple APIs - one for internal use and another for +public users. That leads to duplication of business logic, increased maintenance burden, and the +constant risk of the two drifting out of sync. Having **a single source of truth in one API** +ensures consistency, reduces overhead, and allows you to evolve your system with confidence. + +So how do you **evolve a monolithic GraphQL schema into a safe, public API** while keeping +everything unified? + +The answer: **GraphQL Federation** and **Schema Contracts**. + +## Step 1: Treat the Monolith as a Subgraph + +Before exposing your schema, the first step is to make your monolith federation-compatible. + +Federation is often associated with microservices, but you don’t need dozens of subgraphs to benefit +from it. A monolithic schema can also be treated as a subgraph. All it takes is a few federation +directives: + +```graphql +extend schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@tag"]) +``` + +Now your monolith can participate in the same contract-based filtering that federated graphs use. + +## Step 2: Use Tags to Mark What’s Public + +Next, we need a way to label which parts of the schema are safe to expose. The `@tag` directive is a +simple but powerful tool for this: + +```graphql +type Query { + publicInfo: String @tag(name: "public") + privateInfo: String +} +``` + +By tagging fields, you can later generate a **contract schema** that only includes the safe, +public-facing parts of your internal API. + +--- + +## Step 3: Define a Public Contract + +Once tagging is in place, you can generate a **contract schema**: + +```graphql +type Query { + publicInfo: String +} +``` + +This filtered contract becomes your **public API schema**, while your full internal schema continues +to serve your own applications. + +There are a few ways to create a contract schema: + +**1. Using Hive Console or other schema registry** + +If you’re working with a hosted schema registry, like +[**Hive Console**](https://the-guild.dev/graphql/hive), you can: + +- Define a new contract and select which tags (e.g., `public`) to include. +- Automatically generate and validate the filtered schema whenever a new subgraph is published. +- Take advantage of features like usage analytics and breaking change detection to **collaborate + safely** and ensure consistency across contributors. + +[Learn more in the Hive Console documentation](https://the-guild.dev/graphql/hive/docs/schema-registry/contracts) + +**2. Using a CLI or Library** + +You can also use our +[MIT licensed JavaScript library for Federation Composition](https://github.com/graphql-hive/federation-composition) +to generate the contract programmatically from your monolith. + +## Step 4: Serve the Public Schema Contract + +Creating a filtered schema is only useful if clients can actually query it. Once you have your +contract, you need to **serve it as your public API**. + +The good news: **any federation-compatible router that supports supergraphs can serve a federation +contract**. Popular choices include Apollo Gateway, +[Hive Gateway](https://the-guild.dev/graphql/hive/gateway), or +[Hive Router](https://github.com/graphql-hive/router). + +When serving your contract you deploy the Gateway that consumes the schema contract from the schema +registry to your infrastructure. + +Additionally, you can then configure things like authentication, rate limiting, and access policies. + +Clients can now consume the public API fields by pointing to the gateway, while the internal schema +remains private. + +As a additional measure you can leverage persisted documents to avoid execution of arbitary GraphQL +operations against the private schema. + +For more guidance on choosing a gateway for your project, refer to the +[Federation Gateway Audit](https://the-guild.dev/graphql/hive/federation-gateway-audit) for feature +compatibility and the +[Federation Gateway Performance Benchmark](https://the-guild.dev/graphql/hive/federation-gateway-performance) +for performance considerations. + +## Step 5: Evolve the Public Schema Incrementally + +Federation contracts let you add fields to the public schema **at your own pace**. + +For example, when you decide to open up a mutation: + +```graphql +type Mutation { + publishData(input: PublishInput!): PublishResult! @tag(name: "public") +} +``` + +Tag it, release the new version of your GraphQL schema, regenerate the contract, and the public +schema expands automatically. + +Iterate and refactor your schema internally, then make it public when you are ready. + +No risky schema forks, no duplication, just maintain a **single, unified GraphQL API** while safely +evolving your public interface. + +## Conclusion + +GraphQL Federation isn’t just for distributed architectures. It’s also a powerful tool for +**partitioning access within a monolith**. + +By combining federation contracts with tagging, you can safely evolve a private schema into a public +one, while only exposing the parts you want today, and leaving the door open for more tomorrow. + +This approach provides a clean, incremental path to offering a public GraphQL API without +compromising the flexibility of your internal schema. From 3d0cfde931d12670eb804be7ab681e6f259e6b26 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 15 Sep 2025 10:43:09 +0200 Subject: [PATCH 2/4] adjust content a bit --- .../page.mdx | 86 +++++++++++++++++-- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx index f274ad1f49..368152fe39 100644 --- a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx +++ b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx @@ -89,10 +89,59 @@ If you’re working with a hosted schema registry, like **2. Using a CLI or Library** -You can also use our +If you not yet have adopted a schema registry, you can also use our [MIT licensed JavaScript library for Federation Composition](https://github.com/graphql-hive/federation-composition) to generate the contract programmatically from your monolith. +```ts +import { parse } from 'graphql' +import { composeSchemaContract } from '@theguild/federation-composition' + +const result = composeSchemaContract( + [ + { + name: 'monolith', + typeDefs: parse(/* GraphQL */ ` + type Query { + publicInfo: String @tag(name: "public") + privateInfo: String + } + `) + } + ], + /** Tags to include and exclude */ + { + include: new Set(['public']), + exclude: new Set() + }, + /** Exclude unreachable types */ + true +) + +// This is the filtered schema! +console.log(result.publicSdl) +``` + +Then you can simply create a private schema, similar to the following + +```ts +import { createSchema } from 'graphql-yoga' +import { composeSchemaContract } from '@theguild/federation-composition' +import { resolvers } from './resolvers' + +// ... + +const publicSchema = createSchema({ + typeDefs: parse(result.publicSdl), + resolvers, + resolverValidationOptions: { + // The resolvers still contain the ones of the public schema + // Instead of filtering them out ignoring it is good enough. + requireResolversToMatchSchema: 'ignore' + } +}) +``` + ## Step 4: Serve the Public Schema Contract Creating a filtered schema is only useful if clients can actually query it. Once you have your @@ -103,16 +152,18 @@ contract**. Popular choices include Apollo Gateway, [Hive Gateway](https://the-guild.dev/graphql/hive/gateway), or [Hive Router](https://github.com/graphql-hive/router). -When serving your contract you deploy the Gateway that consumes the schema contract from the schema -registry to your infrastructure. +If using Hive Console as a schema registry, point your gateway to +[the contract supergraph endpoint](https://the-guild.dev/graphql/hive/docs/schema-registry/contracts#access-contract-cdn-artifacts) +to have it expose the public API. Additionally, you can then configure things like authentication, rate limiting, and access policies. Clients can now consume the public API fields by pointing to the gateway, while the internal schema remains private. -As a additional measure you can leverage persisted documents to avoid execution of arbitary GraphQL -operations against the private schema. +As a additional security measure you should leverage +[persisted documents to avoid execution of arbitary GraphQL operations](https://the-guild.dev/graphql/hive/docs/gateway/persisted-documents) +against the private schema. For more guidance on choosing a gateway for your project, refer to the [Federation Gateway Audit](https://the-guild.dev/graphql/hive/federation-gateway-audit) for feature @@ -120,6 +171,23 @@ compatibility and the [Federation Gateway Performance Benchmark](https://the-guild.dev/graphql/hive/federation-gateway-performance) for performance considerations. +As mentioned before, if you are not relying on a schema registry you can simply use and GraphQL +server for serving the public schema. + +```ts filename="Example GraphQL Yoga" +import { createServer } from 'node:http' +import { createSchema, createYoga } from 'graphql-yoga' +import { publicSchema } from './public-schema' + +const server = createServer( + createYoga({ + schema: publicSchema + }) +) + +server.listen(8080) +``` + ## Step 5: Evolve the Public Schema Incrementally Federation contracts let you add fields to the public schema **at your own pace**. @@ -127,6 +195,10 @@ Federation contracts let you add fields to the public schema **at your own pace* For example, when you decide to open up a mutation: ```graphql +input PublishInput @tag(name: "public") { + data: String! +} + type Mutation { publishData(input: PublishInput!): PublishResult! @tag(name: "public") } @@ -150,3 +222,7 @@ one, while only exposing the parts you want today, and leaving the door open for This approach provides a clean, incremental path to offering a public GraphQL API without compromising the flexibility of your internal schema. + +[Learn more on schema contracts with Hive +Console]([Learn more in the Hive Console documentation](https://the-guild.dev/graphql/hive/docs/schema-registry/contracts). +). From 1718fa9b249d5fc7f3c6594f4a17c31eb87e213c Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 15 Sep 2025 10:44:08 +0200 Subject: [PATCH 3/4] update date --- .../(posts)/public-graphql-schema-federation-contracts/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx index 368152fe39..fb53a959f0 100644 --- a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx +++ b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx @@ -2,7 +2,7 @@ title: Incrementally Exposing a Public GraphQL API with Federation Contracts authors: laurin tags: [federation, graphql, hive] -date: 2025-09-09 +date: 2025-09-16 description: 'Learn how to safely expose a public GraphQL API from a monolith using federation contracts and tags, enabling incremental and controlled schema evolution.' From 0433324eaa250917bc46bf3c0f1e9fd680ebd9b2 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Mon, 22 Sep 2025 16:19:25 +0200 Subject: [PATCH 4/4] update tha date --- .../(posts)/public-graphql-schema-federation-contracts/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx index fb53a959f0..2412b096f9 100644 --- a/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx +++ b/packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts/page.mdx @@ -2,7 +2,7 @@ title: Incrementally Exposing a Public GraphQL API with Federation Contracts authors: laurin tags: [federation, graphql, hive] -date: 2025-09-16 +date: 2025-09-22 description: 'Learn how to safely expose a public GraphQL API from a monolith using federation contracts and tags, enabling incremental and controlled schema evolution.'