One of the most powerful ways to use GraphQL is to combine two distinct GraphQL schemas, allowing us to create aggregate queries that can load data from multiple back-ends. This is called schema stitching.
In this tutorial, we’ll learn how easy it is to stitch together two GrAMPS data sources.
To get started, let’s create our first data source using the GraphQL CLI
# Use npx to run the command without having to install anything globallynpx graphql-cli create -b gramps-graphql/data-source-base data-source-stitchingtest# Move into the folder that was just createdcd $_
In src/index.js
, we can declare the entire data source for the sake of simplicity:
const getContext = (_, __, ctx) => Object.keys(ctx);export default {namespace: 'StitchingTest',typeDefs: `type Query {getContext: [String]getById(id: ID!): STX_Test}type STX_Test {id: IDvalue: String}`,context: {getValue: id => ({id,value: `from StitchingTest with ID “${id}”`,}),},resolvers: {Query: {getContext,getById: (_, { id }, ctx) => ctx.getValue(id),},},};
This data source exposes two queries:
getContext
— returns an array of object keys that are present in the data source’s context
objectgetById
— exposes id
and value
fieldsLet’s test this out by running the data source:
yarn dev
At http://localhost:8080/playground, run the following query:
{getContextgetById(id: 3) {value}}
We should see the following return value:
{"data": {"getContext": ["getValue"],"getById": {"value": "from StitchingTest with ID “3”"}}}
So far so good.
Next, let’s add some schema stitching to the existing data source, just to make sure it’s working the way we expected.
In src/index.js
, add a stitching
property with the following definitions:
export default {namespace: 'StitchingTest',typeDefs: `...`,context: { /* ... */ },resolvers: { /* ... */ },stitching: {linkTypeDefs: `extend type Query {getStitchingContext: [String]}`,resolvers: () => ({Query: {getStitchingContext: getContext,},}),},};
Restart the data source in your terminal (ctrl
+ C
to stop, yarn dev
to start), head to http://localhost:8080/playground, and run the following query:
{getContext+ getStitchingContextgetById(id: 3) {value}}
We should see the following return value:
{"data": {"getContext": ["getValue"],"getStitchingContext": ["StitchingTest"],"getById": {"value": "from StitchingTest with ID “3”"}}}
NOTE: Notice that the contexts are different in
getStitchingContext
. This happens because each data source scopes its context to its own namespace to prevent accidentally relying on another data source’s context. However, schema stitching does rely on multiple data source’s contexts, so we include all of the data sources’ contexts.
Next, let’s create a second data source so we can set up more realistic schema stitching.
In your terminal, move into the same directory where your first data source was created, then run the following to create a second data source:
# Use npx to run the command without having to install anything globallynpx graphql-cli create -b gramps-graphql/data-source-base data-source-stitchingtwo# Move into the folder that was just createdcd $_
In src/index.js
, create the second data source all in one place:
export default {namespace: 'StitchingTwo',typeDefs: `type Query {getSomeValues(val: ID): ST2_Values}type ST2_Values {foo: Stringbar: Stringbat: String}`,context: {getSomeValues: val => ({foo: `Schema (val: ${val})`,bar: `Stitching (val: ${val})`,bat: `Rules (val: ${val})`,}),},resolvers: {Query: {getSomeValues: (_, { val }, ctx) => ctx.getSomeValues(val),},},};
This data source is pretty bare bones: it has a single query — getSomeValues
— that exposes three fields that have text and echo the val
the query was called with.
To test it, let’s fire up the new data source along with the original data source:
yarn dev --data-source ../data-source-stitchingtest
NOTE:
yarn dev
is shorthand forgramps dev --data-source .
, so what we’re doing here is effectively runninggramps dev --data-source . --data-source ../data-source-stitchingtest
Open http://localhost:8080/playground and update the query to call getSomeValues
:
{getContextgetStitchingContextgetById(id: 3) {value}+ getSomeValues(val: 2) {+ foo+ bar+ bat+ }}
The output should be:
{"data": {"getContext": ["getValue"],"getStitchingContext": ["StitchingTwo","StitchingTest"],"getById": {"value": "from StitchingTest with ID “3”"},"getSomeValues": {"foo": "Schema (val: 2)","bar": "Stitching (val: 2)","bat": "Rules (val: 2)"}}}
Finally, let’s add stitching
config to tie the two data source together. In the second data source, add the following:
export default {namespace: 'StitchingTwo',typeDefs: `...`,context: { /* ... */ },resolvers: { /* ... */ },stitching: {linkTypeDefs: `extend type STX_Test {stitched: ST2_Values}`,resolvers: mergeInfo => ({STX_Test: {stitched: {fragment: 'fragment StitchingTestField on STX_Test { id }',resolve: ({ id }, args, context, info) =>mergeInfo.delegate('query','getSomeValues',{ val: id },context,info,),},},}),},};
First, we use linkTypeDefs
to extend the STX_Test
type by adding a new field called stitched
.
Then, in resolvers
, we set up stitched
— which is a field on our first data source, remember — to get its value from the getSomeValues
query, which is in the second data source.
Under the hood, this is done using mergeSchemas
. Be sure to check that out for additional information about how schema stitching happens, and some of the different ways you can work with it.
With the stitching config in place, let’s fire it up and test it.
Run the following command to start a gateway with both data sources:
yarn dev --data-source ../data-source-stitchingtest
Then, open http://localhost:8080/playground and add the stitching
field to the query:
{getContextgetStitchingContextgetById(id: 3) {value+ stitched {+ foo+ bar+ bat+ }}getSomeValues(val: 2) {foobarbat}}
Once executed, we’ll see the following:
{"data": {"getContext": ["getValue"],"getStitchingContext": ["StitchingTwo","StitchingTest"],"getById": {"value": "from StitchingTest with ID “3”","stitched": {"foo": "Schema (val: 3)","bar": "Stitching (val: 3)","bat": "Rules (val: 3)"}},"getSomeValues": {"foo": "Schema (val: 2)","bar": "Stitching (val: 2)","bat": "Rules (val: 2)"}}}
And that’s it! We now have one data source including data from a second data source as part of its own queries.