Skip to content

Commit 3dd4019

Browse files
authored
docs(blog): public graphql schema federation contracts (#6952)
1 parent ed76b38 commit 3dd4019

File tree

1 file changed

+228
-0
lines changed
  • packages/web/docs/src/app/blog/(posts)/public-graphql-schema-federation-contracts

1 file changed

+228
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
---
2+
title: Incrementally Exposing a Public GraphQL API with Federation Contracts
3+
authors: laurin
4+
tags: [federation, graphql, hive]
5+
date: 2025-09-22
6+
description:
7+
'Learn how to safely expose a public GraphQL API from a monolith using federation contracts and
8+
tags, enabling incremental and controlled schema evolution.'
9+
---
10+
11+
Many teams start with a **single GraphQL monolith** powering their applications. Over time, the need
12+
arises to expose parts of that schema publicly - whether for partners, customers, or other external
13+
integrations.
14+
15+
But here’s the problem:
16+
17+
Your internal schema wasn’t designed for public consumption. It likely contains inconsistent naming,
18+
experimental fields, and sensitive operations you don’t want outsiders touching.
19+
20+
At the same time, you don’t want to maintain multiple APIs - one for internal use and another for
21+
public users. That leads to duplication of business logic, increased maintenance burden, and the
22+
constant risk of the two drifting out of sync. Having **a single source of truth in one API**
23+
ensures consistency, reduces overhead, and allows you to evolve your system with confidence.
24+
25+
So how do you **evolve a monolithic GraphQL schema into a safe, public API** while keeping
26+
everything unified?
27+
28+
The answer: **GraphQL Federation** and **Schema Contracts**.
29+
30+
## Step 1: Treat the Monolith as a Subgraph
31+
32+
Before exposing your schema, the first step is to make your monolith federation-compatible.
33+
34+
Federation is often associated with microservices, but you don’t need dozens of subgraphs to benefit
35+
from it. A monolithic schema can also be treated as a subgraph. All it takes is a few federation
36+
directives:
37+
38+
```graphql
39+
extend schema
40+
@link(url: "https://specs.apollo.dev/link/v1.0")
41+
@link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@tag"])
42+
```
43+
44+
Now your monolith can participate in the same contract-based filtering that federated graphs use.
45+
46+
## Step 2: Use Tags to Mark What’s Public
47+
48+
Next, we need a way to label which parts of the schema are safe to expose. The `@tag` directive is a
49+
simple but powerful tool for this:
50+
51+
```graphql
52+
type Query {
53+
publicInfo: String @tag(name: "public")
54+
privateInfo: String
55+
}
56+
```
57+
58+
By tagging fields, you can later generate a **contract schema** that only includes the safe,
59+
public-facing parts of your internal API.
60+
61+
---
62+
63+
## Step 3: Define a Public Contract
64+
65+
Once tagging is in place, you can generate a **contract schema**:
66+
67+
```graphql
68+
type Query {
69+
publicInfo: String
70+
}
71+
```
72+
73+
This filtered contract becomes your **public API schema**, while your full internal schema continues
74+
to serve your own applications.
75+
76+
There are a few ways to create a contract schema:
77+
78+
**1. Using Hive Console or other schema registry**
79+
80+
If you’re working with a hosted schema registry, like
81+
[**Hive Console**](https://the-guild.dev/graphql/hive), you can:
82+
83+
- Define a new contract and select which tags (e.g., `public`) to include.
84+
- Automatically generate and validate the filtered schema whenever a new subgraph is published.
85+
- Take advantage of features like usage analytics and breaking change detection to **collaborate
86+
safely** and ensure consistency across contributors.
87+
88+
[Learn more in the Hive Console documentation](https://the-guild.dev/graphql/hive/docs/schema-registry/contracts)
89+
90+
**2. Using a CLI or Library**
91+
92+
If you not yet have adopted a schema registry, you can also use our
93+
[MIT licensed JavaScript library for Federation Composition](https://github.com/graphql-hive/federation-composition)
94+
to generate the contract programmatically from your monolith.
95+
96+
```ts
97+
import { parse } from 'graphql'
98+
import { composeSchemaContract } from '@theguild/federation-composition'
99+
100+
const result = composeSchemaContract(
101+
[
102+
{
103+
name: 'monolith',
104+
typeDefs: parse(/* GraphQL */ `
105+
type Query {
106+
publicInfo: String @tag(name: "public")
107+
privateInfo: String
108+
}
109+
`)
110+
}
111+
],
112+
/** Tags to include and exclude */
113+
{
114+
include: new Set(['public']),
115+
exclude: new Set()
116+
},
117+
/** Exclude unreachable types */
118+
true
119+
)
120+
121+
// This is the filtered schema!
122+
console.log(result.publicSdl)
123+
```
124+
125+
Then you can simply create a private schema, similar to the following
126+
127+
```ts
128+
import { createSchema } from 'graphql-yoga'
129+
import { composeSchemaContract } from '@theguild/federation-composition'
130+
import { resolvers } from './resolvers'
131+
132+
// ...
133+
134+
const publicSchema = createSchema({
135+
typeDefs: parse(result.publicSdl),
136+
resolvers,
137+
resolverValidationOptions: {
138+
// The resolvers still contain the ones of the public schema
139+
// Instead of filtering them out ignoring it is good enough.
140+
requireResolversToMatchSchema: 'ignore'
141+
}
142+
})
143+
```
144+
145+
## Step 4: Serve the Public Schema Contract
146+
147+
Creating a filtered schema is only useful if clients can actually query it. Once you have your
148+
contract, you need to **serve it as your public API**.
149+
150+
The good news: **any federation-compatible router that supports supergraphs can serve a federation
151+
contract**. Popular choices include Apollo Gateway,
152+
[Hive Gateway](https://the-guild.dev/graphql/hive/gateway), or
153+
[Hive Router](https://github.com/graphql-hive/router).
154+
155+
If using Hive Console as a schema registry, point your gateway to
156+
[the contract supergraph endpoint](https://the-guild.dev/graphql/hive/docs/schema-registry/contracts#access-contract-cdn-artifacts)
157+
to have it expose the public API.
158+
159+
Additionally, you can then configure things like authentication, rate limiting, and access policies.
160+
161+
Clients can now consume the public API fields by pointing to the gateway, while the internal schema
162+
remains private.
163+
164+
As a additional security measure you should leverage
165+
[persisted documents to avoid execution of arbitary GraphQL operations](https://the-guild.dev/graphql/hive/docs/gateway/persisted-documents)
166+
against the private schema.
167+
168+
For more guidance on choosing a gateway for your project, refer to the
169+
[Federation Gateway Audit](https://the-guild.dev/graphql/hive/federation-gateway-audit) for feature
170+
compatibility and the
171+
[Federation Gateway Performance Benchmark](https://the-guild.dev/graphql/hive/federation-gateway-performance)
172+
for performance considerations.
173+
174+
As mentioned before, if you are not relying on a schema registry you can simply use and GraphQL
175+
server for serving the public schema.
176+
177+
```ts filename="Example GraphQL Yoga"
178+
import { createServer } from 'node:http'
179+
import { createSchema, createYoga } from 'graphql-yoga'
180+
import { publicSchema } from './public-schema'
181+
182+
const server = createServer(
183+
createYoga({
184+
schema: publicSchema
185+
})
186+
)
187+
188+
server.listen(8080)
189+
```
190+
191+
## Step 5: Evolve the Public Schema Incrementally
192+
193+
Federation contracts let you add fields to the public schema **at your own pace**.
194+
195+
For example, when you decide to open up a mutation:
196+
197+
```graphql
198+
input PublishInput @tag(name: "public") {
199+
data: String!
200+
}
201+
202+
type Mutation {
203+
publishData(input: PublishInput!): PublishResult! @tag(name: "public")
204+
}
205+
```
206+
207+
Tag it, release the new version of your GraphQL schema, regenerate the contract, and the public
208+
schema expands automatically.
209+
210+
Iterate and refactor your schema internally, then make it public when you are ready.
211+
212+
No risky schema forks, no duplication, just maintain a **single, unified GraphQL API** while safely
213+
evolving your public interface.
214+
215+
## Conclusion
216+
217+
GraphQL Federation isn’t just for distributed architectures. It’s also a powerful tool for
218+
**partitioning access within a monolith**.
219+
220+
By combining federation contracts with tagging, you can safely evolve a private schema into a public
221+
one, while only exposing the parts you want today, and leaving the door open for more tomorrow.
222+
223+
This approach provides a clean, incremental path to offering a public GraphQL API without
224+
compromising the flexibility of your internal schema.
225+
226+
[Learn more on schema contracts with Hive
227+
Console]([Learn more in the Hive Console documentation](https://the-guild.dev/graphql/hive/docs/schema-registry/contracts).
228+
).

0 commit comments

Comments
 (0)