{"id":10,"date":"2026-06-06T21:48:19","date_gmt":"2026-06-06T21:48:19","guid":{"rendered":"https:\/\/softwareproduction.eu\/wordpress\/?p=10"},"modified":"2026-06-07T01:21:36","modified_gmt":"2026-06-07T01:21:36","slug":"graphql-schema-stitching-one-api-to-rule-them-all","status":"publish","type":"post","link":"https:\/\/softwareproduction.eu\/?p=10","title":{"rendered":"GraphQL Schema Stitching \u2014 One API to Rule Them All"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Third in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Problem: Frontend as Integration Middleware<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every non-trivial web application pulls data from more than one source. A common pattern is a headless CMS for marketing content and page structure, paired with a backend API for business logic such as pricing, order processing, and user data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In legacy architectures, the frontend becomes the integration layer. Each page assembles its view by calling multiple APIs, each with its own authentication, error handling, response format, and rate limiting. The frontend developer is no longer writing UI code \u2014 they are writing middleware.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph Browser[\"Frontend (Browser)\"]\n    direction TB\n    Page[\"Page Component\"]\n  end\n\n  subgraph CMS[\"CMS REST API\"]\n    CmsPage[\"\/api\/cms\/page\/homepage\"]\n    CmsNav[\"\/api\/cms\/navigation\"]\n  end\n\n  subgraph Backend[\"Backend REST API\"]\n    Subscriptions[\"\/api\/backend\/subscriptions?zip=10115\"]\n    Offers[\"\/api\/backend\/offers\"]\n  end\n\n  ThirdParty[\"\/api\/ratings\\n(Third-party API)\"]\n\n  Page --&gt;|\"fetch('\/api\/cms\/page\/homepage')\"| CmsPage\n  Page --&gt;|\"fetch('\/api\/cms\/navigation')\"| CmsNav\n  Page --&gt;|\"fetch('\/api\/backend\/subscriptions?zip=10115')\"| Subscriptions\n  Page --&gt;|\"fetch('\/api\/backend\/offers')\"| Offers\n  Page --&gt;|\"fetch('\/api\/ratings')\"| ThirdParty<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Every new feature means working out which endpoints to call, in what order, and how to merge the responses. The frontend is doing work the backend should own.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What Schema Stitching Changes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Schema stitching<\/strong> merges multiple GraphQL schemas into a single unified schema. The frontend sees one endpoint, one type system, and one query language. It never needs to know which backend produced which field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The idea is simple: an Apollo Server gateway running inside the Nuxt application fetches the schemas of all data sources at startup, merges them, and serves the result at a single <code>\/api\/graphql<\/code> endpoint.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  Browser[\"Browser\\nSingle GraphQL Query\"] --&gt;|HTTP \/api\/graphql| Nuxt[\"Nuxt 4 (SSR + Gateway)\\nApollo Server at \/api\/graphql\"]\n\n  subgraph Gateway[\"Apollo Gateway\"]\n    Schema[\"Stitched Schema\\n(merged at startup)\"]\n  end\n\n  Nuxt --&gt; Schema\n\n  subgraph CMS[\"Headless CMS Subgraph\"]\n    CmsTypes[\"content, pages, navigation\"]\n  end\n\n  subgraph BE[\"Backend Subgraph\"]\n    BeTypes[\"pricing, orders, user data\"]\n  end\n\n  Schema --&gt;|\"resolve CMS fields\"| CMS\n  Schema --&gt;|\"resolve backend fields\"| BE<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A single query can fetch data from both sources. The gateway resolves each field from the correct subgraph transparently:<\/p>\n\n\n\n<pre><code class=\"language-graphql\">query ProductPage($path: String!) {\n  page(path: $path) {           # \u2190 resolved from CMS\n    title\n    content { ... }\n  }\n  offers(searchTerm: \"xyz\") {   # \u2190 resolved from backend\n    name\n    price\n    features\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">One request, one response, one set of types. No manual data merging.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>@delegate<\/code> Directive \u2014 Cross-Subgraph Field Resolution<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Merging schemas is only the beginning. The real value appears when data from one subgraph needs to be <strong>enriched<\/strong> with data from another.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example: the backend returns product add-ons, each with a technical <code>addonId<\/code>. The CMS stores marketing descriptions for those add-ons, keyed by a slug derived from that ID. Traditionally, the frontend makes two calls and joins the data by hand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With the <code>@delegate<\/code> directive, that enrichment is declared directly in the schema:<\/p>\n\n\n\n<pre><code class=\"language-graphql\">extend type AddonDetailsModel {\n  description: String\n    @delegate(\n      to: \"cms\",\n      field: \"content\",\n      args: { id: \"{addonId:slug}\" }\n    )\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This says: <em>when someone requests <code>description<\/code> on an <code>AddonDetailsModel<\/code>, take the <code>addonId<\/code> from the parent object, apply the <code>slug<\/code> formatter, and use it to query the <code>content<\/code> field on the CMS subgraph.<\/em><\/p>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant C as Client\n  participant G as GraphQL Gateway\n  participant B as Backend Subgraph\n  participant M as CMS Subgraph\n\n  C-&gt;&gt;G: query { addonDetails { addonId, name, description } }\n  G-&gt;&gt;B: addonDetails\n  B--&gt;&gt;G: { addonId: \"Premium Support\", name: \"...\" }\n\n  Note over G: Step 2: @delegate for description&lt;br\/&gt;Extract addonId, apply :slug \u2192 \"premium-support\"\n\n  G-&gt;&gt;M: content(id: \"premium-support\")\n  M--&gt;&gt;G: { description: \"Our premium support package...\" }\n\n  Note over G: Step 3: Merge CMS result into backend response\n\n  G--&gt;&gt;C: { addonId, name, description }<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The resolution pipeline looks like this:<\/p>\n\n\n\n<pre><code>Client Query: addonDetails { addonId, name, description }\n                                                \u2502\n                    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                    \u25bc\nStep 1: Resolve addonDetails from backend subgraph\n        \u2192 { addonId: \"Premium Support\", name: \"...\", ... }\n                    \u2502\n                    \u25bc\nStep 2: @delegate fires for 'description' field\n        \u2192 Extract addonId: \"Premium Support\"\n        \u2192 Apply :slug formatter \u2192 \"premium-support\"\n        \u2192 Query CMS: content(id: \"premium-support\") { ... }\n                    \u2502\n                    \u25bc\nStep 3: Merge CMS result into backend response\n        \u2192 { addonId: \"Premium Support\", name: \"...\",\n            description: \"Our premium support package...\" }\n                    \u2502\n                    \u25bc\nClient receives unified response\n(has no idea two subgraphs were involved)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Placeholder Formatters<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>{value:format}<\/code> syntax handles naming convention mismatches between systems:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Formatter<\/th><th>Input<\/th><th>Output<\/th><th>Use Case<\/th><\/tr><\/thead><tbody><tr><td><code>:slug<\/code><\/td><td><code>\"Premium Support\"<\/code><\/td><td><code>\"premium-support\"<\/code><\/td><td>CMS lookups by slug<\/td><\/tr><tr><td><code>:lower<\/code><\/td><td><code>\"CATEGORY\"<\/code><\/td><td><code>\"category\"<\/code><\/td><td>Case-insensitive matching<\/td><\/tr><tr><td><code>:upper<\/code><\/td><td><code>\"category\"<\/code><\/td><td><code>\"CATEGORY\"<\/code><\/td><td>Enum matching<\/td><\/tr><tr><td>(none)<\/td><td><code>\"abc-123\"<\/code><\/td><td><code>\"abc-123\"<\/code><\/td><td>Direct pass-through<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Typed Placeholders<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">String placeholders always produce strings. But sometimes you need to pass a Boolean or a number across the subgraph boundary. Typed placeholders solve this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>p_preview<\/code> \u2014 reads the <code>preview<\/code> <strong>p<\/strong>arameter from the field&#8217;s arguments, preserving its Boolean type<\/li>\n<li><code>f_categoryId<\/code> \u2014 reads <code>categoryId<\/code> from the parent <strong>f<\/strong>ield&#8217;s resolved object<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>p_<\/code> and <code>f_<\/code> prefixes are a deliberate naming convention that makes the data-flow direction explicit in every delegation declaration.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  subgraph Parent[\"Parent field\/object\"]\n    Fcat[\"f_categoryId\\n(from resolved object)\"]\n  end\n\n  subgraph Args[\"Field arguments\"]\n    Pprev[\"p_preview\\n(from args, Boolean)\"]\n  end\n\n  Gateway[\"Gateway @delegate\\nschema directive\"] --&gt;|reads| Fcat\n  Gateway --&gt;|reads| Pprev\n\n  Fcat --&gt;|\"typed value\"| Target[\"Target subgraph\\n(where: { categoryId: ... })\"]\n  Pprev --&gt;|\"typed value\"| Target<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Caching: Not Everything Is Equal<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Different data sources in a stitched schema have different caching needs:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Source<\/th><th>Freshness Requirement<\/th><th>Cache Strategy<\/th><\/tr><\/thead><tbody><tr><td>CMS content<\/td><td>Minutes to hours<\/td><td>Aggressive (LRU, per-operation)<\/td><\/tr><tr><td>Product pricing<\/td><td>Real-time<\/td><td>No cache<\/td><\/tr><tr><td>Ratings<\/td><td>Hours<\/td><td>Moderate (TTL-based)<\/td><\/tr><tr><td>Navigation<\/td><td>Until content changes<\/td><td>Aggressive + webhook invalidation<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The gateway implements per-operation caching: a whitelist of GraphQL operation names determines which queries are cached and for how long. CMS queries are cached aggressively; pricing queries bypass the cache entirely.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  Client[\"Client\"] --&gt;|\"GraphQL operation\"| Gateway[\"Gateway\"]\n\n  subgraph CacheLayer[\"In-memory cache (per replica)\"]\n    Cache[\"Per-operation entries\\n(keyed by operation name)\"]\n  end\n\n  Gateway --&gt;|\"check\/put\"| Cache\n\n  Cache --&gt;|\"hit\"| Gateway\n  Gateway --&gt;|\"miss \u2192 resolve via subgraph\"| Subgraphs[\"CMS \/ Backend \/ Ratings\"]\n\n  Subgraphs --&gt; Gateway\n  Gateway --&gt;|\"response\"| Client<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Cache Invalidation via Pub\/Sub<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In a multi-replica deployment, cache invalidation must be coordinated. When the CMS publishes new content, it fires a webhook. The first application replica to receive the webhook publishes an invalidation message on a Redis Pub\/Sub channel. All replicas subscribe to that channel and clear their local caches simultaneously.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  CMS[\"CMS\\nContent Change\"] --&gt; Webhook[\"Webhook\"]\n\n  Webhook --&gt; R1[\"Replica #1\"]\n  R1 --&gt;|\"Clear local cache\"| R1Cache[\"Replica #1 cache cleared\"]\n  R1 --&gt;|\"Publish 'invalidate'\\non Redis channel\"| Redis[\"Redis Pub\/Sub\\n'invalidate' channel\"]\n\n  Redis --&gt; R2[\"Replica #2\"]\n  Redis --&gt; R3[\"Replica #3\"]\n  Redis --&gt; R4[\"Replica #4\"]\n\n  R2 --&gt;|\"Clear local cache\"| R2Cache[\"Replica #2 cache cleared\"]\n  R3 --&gt;|\"Clear local cache\"| R3Cache[\"Replica #3 cache cleared\"]\n  R4 --&gt;|\"Clear local cache\"| R4Cache[\"Replica #4 cache cleared\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No stale data on any replica, no manual cache management. The CMS editor publishes content and all replicas serve the new version within seconds.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What This Architecture Eliminates<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Concern<\/th><th>Before (REST Multi-Source)<\/th><th>After (Stitched GraphQL)<\/th><\/tr><\/thead><tbody><tr><td><strong>API calls per page<\/strong><\/td><td>3\u20135 separate HTTP requests<\/td><td>1 GraphQL query<\/td><\/tr><tr><td><strong>Data joining<\/strong><\/td><td>Manual in frontend code<\/td><td>Automatic via <code>@delegate<\/code><\/td><\/tr><tr><td><strong>Type definitions<\/strong><\/td><td>Per-endpoint, per-source<\/td><td>Generated from unified schema<\/td><\/tr><tr><td><strong>Error handling<\/strong><\/td><td>Per-endpoint custom logic<\/td><td>One Apollo error link<\/td><\/tr><tr><td><strong>Retry logic<\/strong><\/td><td>Duplicated per client<\/td><td>One retry link with backoff<\/td><\/tr><tr><td><strong>Cache invalidation<\/strong><\/td><td>Manual or nonexistent<\/td><td>Automatic via Pub\/Sub<\/td><\/tr><tr><td><strong>New data source<\/strong><\/td><td>New REST client + types<\/td><td>New subgraph + stitch config<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Schema stitching is not federation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">GraphQL Federation (Apollo\u2019s managed approach) assumes you control all subgraphs and can add federation directives to each one. Schema stitching works with <strong>any GraphQL endpoint<\/strong>, including third-party APIs you cannot modify. If you are integrating a headless CMS or an external service, stitching is often the only option that fits.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  subgraph Federation[\"GraphQL Federation\"]\n    FedGw[\"Federation Gateway\"]\n    FedSub[\"Subgraphs\\n(with federation directives)\"]\n  end\n\n  subgraph Stitching[\"Schema Stitching\"]\n    StGw[\"Stitching Gateway\"]\n    StAny[\"Any GraphQL endpoints\\n(CMS, 3rd party, internal)\"]\n  end\n\n  FedGw --&gt; FedSub\n  StGw --&gt; StAny\n  StAny -.- NOTE[\"Works with unmodified external GraphQL APIs\"]<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">The gateway belongs in the application, not next to it<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Running the Apollo Server gateway inside the Nuxt application, as a Nitro server handler, means SSR queries never leave the process \u2014 zero network overhead for server-side data fetching. During SSR, the \u201cgateway\u201d is just a function call. On the client, it becomes a normal HTTP request to <code>\/api\/graphql<\/code>.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph SSR[\"Server-Side Rendering (SSR)\"]\n    NuxtSSR[\"Nuxt app process\"]\n    GwSSR[\"Apollo Gateway\\n(in-process)\"]\n  end\n\n  subgraph ClientSide[\"Client-side\"]\n    Browser[\"Browser\"]\n    HttpGw[\"HTTP \/api\/graphql\"]\n  end\n\n  NuxtSSR --&gt;|\"function call\"| GwSSR\n  Browser --&gt;|\"HTTP\"| HttpGw\n  HttpGw --&gt;|\"Nuxt handler\"| NuxtSSR<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Delegation is powerful but easy to misuse<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>@delegate<\/code> directive makes cross-subgraph enrichment easy to declare, which is both useful and risky. Without care, delegation chains create N+1 problems: if a list query returns 50 items and each has a delegated field, that is 50 additional subgraph queries. For list scenarios, design the target subgraph to accept batch arguments (<code>where: { id_in: [...] }<\/code>) or use custom resolver functions that fan out efficiently.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  Client[\"Client\"] --&gt; Query[\"List query\\nitems { id, delegatedField }\"]\n\n  Query --&gt; Gateway[\"Gateway\"]\n\n  subgraph AntiPattern[\"Naive delegation (N+1)\"]\n    Items[\"50 items\"] --&gt;|\"@delegate per item\"| Subgraph1[\"Target subgraph\\n50 separate calls\"]\n  end\n\n  subgraph Optimized[\"Batched delegation\"]\n    Items2[\"50 items\"] --&gt;|\"collect IDs\"| Batch[\"id_in: ...\"]\n    Batch --&gt; Subgraph2[\"Target subgraph\\n1 batched call\"]\n  end<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Placeholder formatters save more effort than they look like<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Naming conventions differ between systems \u2014 camelCase, snake_case, slugified strings. The formatter system handles this directly in the schema declaration. Without it, every delegation would require a custom resolver to transform the argument. With formatters, the transformation is a single annotation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Article 4<\/strong>: <em>The @delegate Directive Deep Dive<\/em> \u2014 Cross-subgraph field resolution with typed placeholders and formatters.<\/li>\n<li><strong>Article 5<\/strong>: <em>GraphQL-Based Code Generation \u2014 Eliminating All Boilerplate<\/em> \u2014 How writing a <code>.graphql<\/code> file generates a fully typed Vue composable with zero manual work.<\/li>\n<li><strong>Article 6<\/strong>: <em>Architecting Enterprise Nuxt with Custom Modules<\/em> \u2014 Why modules are the architecture, not folders.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Munir Husseini is a software architect specializing in full-stack TypeScript, .NET, and cloud-native architectures.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Third in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision. The Problem: Frontend as Integration Middleware Every non-trivial web application pulls data from more than one source. A common pattern is a headless CMS for [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":214,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-10","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced-web-app-with-nuxt-and-net"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>GraphQL Schema Stitching \u2014 One API to Rule Them All - Software Production<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/softwareproduction.eu\/?p=10\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"GraphQL Schema Stitching \u2014 One API to Rule Them All - Software Production\" \/>\n<meta property=\"og:description\" content=\"Third in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision. The Problem: Frontend as Integration Middleware Every non-trivial web application pulls data from more than one source. A common pattern is a headless CMS for [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/softwareproduction.eu\/?p=10\" \/>\n<meta property=\"og:site_name\" content=\"Software Production\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-06T21:48:19+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-07T01:21:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1880\" \/>\n\t<meta property=\"og:image:height\" content=\"1058\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Munir Husseini\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Munir Husseini\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10\"},\"author\":{\"name\":\"Munir Husseini\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\"},\"headline\":\"GraphQL Schema Stitching \u2014 One API to Rule Them All\",\"datePublished\":\"2026-06-06T21:48:19+00:00\",\"dateModified\":\"2026-06-07T01:21:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10\"},\"wordCount\":980,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/03-graphql-schema-stitching.jpg\",\"articleSection\":[\"Advanced Web App with Nuxt and .NET\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10\",\"name\":\"GraphQL Schema Stitching \u2014 One API to Rule Them All - Software Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/03-graphql-schema-stitching.jpg\",\"datePublished\":\"2026-06-06T21:48:19+00:00\",\"dateModified\":\"2026-06-07T01:21:36+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=10\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#primaryimage\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/03-graphql-schema-stitching.jpg\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/03-graphql-schema-stitching.jpg\",\"width\":1880,\"height\":1058},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=10#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/softwareproduction.eu\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"GraphQL Schema Stitching \u2014 One API to Rule Them All\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"name\":\"Softwareproduction\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/softwareproduction.eu\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\",\"name\":\"Munir Husseini\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"width\":32,\"height\":32,\"caption\":\"Munir Husseini\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\",\"name\":\"Munir Husseini\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"caption\":\"Munir Husseini\"},\"sameAs\":[\"https:\\\/\\\/softwareproduction.eu\\\/\"],\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"GraphQL Schema Stitching \u2014 One API to Rule Them All - Software Production","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/softwareproduction.eu\/?p=10","og_locale":"en_US","og_type":"article","og_title":"GraphQL Schema Stitching \u2014 One API to Rule Them All - Software Production","og_description":"Third in a series about migrating from legacy architectures to a modern Nuxt 4 stack. The series covers architecture, code generation, performance, infrastructure, and the automation philosophy behind every decision. The Problem: Frontend as Integration Middleware Every non-trivial web application pulls data from more than one source. A common pattern is a headless CMS for [&hellip;]","og_url":"https:\/\/softwareproduction.eu\/?p=10","og_site_name":"Software Production","article_published_time":"2026-06-06T21:48:19+00:00","article_modified_time":"2026-06-07T01:21:36+00:00","og_image":[{"width":1880,"height":1058,"url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg","type":"image\/jpeg"}],"author":"Munir Husseini","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Munir Husseini","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/softwareproduction.eu\/?p=10#article","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/?p=10"},"author":{"name":"Munir Husseini","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f"},"headline":"GraphQL Schema Stitching \u2014 One API to Rule Them All","datePublished":"2026-06-06T21:48:19+00:00","dateModified":"2026-06-07T01:21:36+00:00","mainEntityOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=10"},"wordCount":980,"commentCount":0,"publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=10#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg","articleSection":["Advanced Web App with Nuxt and .NET"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/softwareproduction.eu\/?p=10#respond"]}]},{"@type":"WebPage","@id":"https:\/\/softwareproduction.eu\/?p=10","url":"https:\/\/softwareproduction.eu\/?p=10","name":"GraphQL Schema Stitching \u2014 One API to Rule Them All - Software Production","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/#website"},"primaryImageOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=10#primaryimage"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=10#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg","datePublished":"2026-06-06T21:48:19+00:00","dateModified":"2026-06-07T01:21:36+00:00","breadcrumb":{"@id":"https:\/\/softwareproduction.eu\/?p=10#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/softwareproduction.eu\/?p=10"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/?p=10#primaryimage","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg","width":1880,"height":1058},{"@type":"BreadcrumbList","@id":"https:\/\/softwareproduction.eu\/?p=10#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/softwareproduction.eu\/"},{"@type":"ListItem","position":2,"name":"GraphQL Schema Stitching \u2014 One API to Rule Them All"}]},{"@type":"WebSite","@id":"https:\/\/softwareproduction.eu\/#website","url":"https:\/\/softwareproduction.eu\/","name":"Softwareproduction","description":"","publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/softwareproduction.eu\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/softwareproduction.eu\/#organization","name":"Munir Husseini","url":"https:\/\/softwareproduction.eu\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","width":32,"height":32,"caption":"Munir Husseini"},"image":{"@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f","name":"Munir Husseini","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","caption":"Munir Husseini"},"sameAs":["https:\/\/softwareproduction.eu\/"],"url":"https:\/\/softwareproduction.eu\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/03-graphql-schema-stitching.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/10","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10"}],"version-history":[{"count":10,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/10\/revisions"}],"predecessor-version":[{"id":215,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/10\/revisions\/215"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/media\/214"}],"wp:attachment":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}