stitchSchema returns null

Viewed 52

I have stitched two schemas together and run on localhost to query it. But the query returns null for the data in the second schema and I am not sure why.

I have the following code to stitch to remote schemas together and run a localhost graphql server to serve it. It should add the linked data from the second schema under cmsMetaData in the main Product data. But cmsMetaData is null.

import { ApolloServer } from 'apollo-server-micro';
import { ApolloServerPluginInlineTraceDisabled, ApolloServerPluginLandingPageLocalDefault } from "apollo-server-core";
import { stitchSchemas } from '@graphql-tools/stitch';
import { delegateToSchema } from '@graphql-tools/delegate';
import { RenameTypes, RenameRootFields } from '@graphql-tools/wrap';

import createRemoteSchema from '../../utils/createRemoteExecutor';

// Configuration for Next.js API Routes
export const config = {
    api: {
        bodyParser: false,
    },
};

// Export as a Next.js API Route
export default async (req, res) => {
    
    // Setup subschema configurations
    const productsSubschema = await createRemoteSchema({
        url: 'https://schema1.com/graphql/'
    });
    
    const cmsSubschema = await createRemoteSchema({
        url: 'https://schema2.com/graphql/',
        transforms: [
            new RenameRootFields(
                (operationName, fieldName, fieldConfig) => `strapi_${fieldName}`,
            ),
            new RenameTypes((name) => `Strapi_${name}`),
        ],
    });
    
    // Build the combined schema and set up the extended schema and resolver
    const schema = stitchSchemas({
        subschemas: [productsSubschema, cmsSubschema],
        typeDefs: `
        extend type Product {
            cmsMetaData: Strapi_Product
        }
        `,
        resolvers: {
            Product: {
                cmsMetaData: {
                    selectionSet: `{ id }`,
                    resolve(product, args, context, info) {
                        
                        // Get the data for the extended type from the subschema for Strapi
                        return delegateToSchema({
                            schema: cmsSubschema,
                            operation: 'query',
                            fieldName: 'strapi_product',
                            args: { where: { SaleorID: product.id } },
                            context,
                            info,
                        });
                    },
                },
            },
        },
    });

    // Set up the GraphQL server
    const apolloServer = new ApolloServer({
        schema,
        plugins: [
            ApolloServerPluginInlineTraceDisabled(),
            ApolloServerPluginLandingPageLocalDefault({ embed: true }),
        ],
    });
    await apolloServer.start();
    const apolloServerHandler = apolloServer.createHandler({
        path: '/api/graphql',
    });

    // Return the GraphQL endpoint
    return apolloServerHandler(req, res);
};

utils/createRemoteExecutor.js is:

import { introspectSchema, wrapSchema } from '@graphql-tools/wrap';
import { print } from 'graphql';

// Builds a remote schema executor function,
// customize any way that you need (auth, headers, etc).
// Expects to recieve an object with "document" and "variable" params,
// and asynchronously returns a JSON response from the remote.
export default async function createRemoteSchema({ url, ...filters }) {
    const executor = async ({ document, variables }) => {
        const query = print(document);
        const fetchResult = await fetch(url, {
            method: 'POST',
            headers: {
                // We can also do Authentication here
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ query, variables }),
        });
        return fetchResult.json();
    };

    return wrapSchema({
        schema: await introspectSchema(executor),
        executor,
        ...filters,
    });
}

The query is:

products(first: 100, channel: "default-channel")
{
    edges
    {
        node
        {
            id
            name        
            cmsMetaData
            {
                Title
                SaleorID
            }
        }
    }
}

In my api.tsx, which I generate using codegen.yaml, Product contains cmsMetaData as follows, which is of type Strapi_Product:

export type Product = Node & ObjectWithMetadata & {
    __typename?: 'Product';
    ... 
    cmsMetaData?: Maybe<Array<Maybe<Strapi_Product>>>;
    ...
}

Strapi_Product is as follows which contains Title, SaleorID etc.:

export type Strapi_Product = {
    __typename?: 'Strapi_Product';
    SaleorID?: Maybe<Scalars['String']>;
    Title?: Maybe<Scalars['String']>;
    createdAt?: Maybe<Scalars['Strapi_DateTime']>;
    publishedAt?: Maybe<Scalars['Strapi_DateTime']>;
    updatedAt?: Maybe<Scalars['Strapi_DateTime']>;
};

But the date in GraphQL shows null for cmsMetaData as null:

{
  "data": {
    "products": {
      "edges": [
        {
          "node": {
            "id": "UHJvZHVjdDoxMjc=",
            "name": "52-00 Base Plate",
            "cmsMetaData": null
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMjg=",
            "name": "52-01HD Weigh Module",
            "cmsMetaData": null
          }
        }
      ]
    }
  }
}
1 Answers

Your problem seems related to this github issue. Most of your code looks totally fine, so I guess it fetches the schema correctly. The cmsMetaData field is null because it did not find anything matching objects using the selection criteria. This unwanted behavior is in the transformation and/or the resolver.

A good starting point for debugging would be to remove the RenameRootFields mutation. Furthermore, this example looks like your use case, it is an excellent step-by-step guide.

I also rebuild your example from an example I found on the internet. You most probably have a typo in one of the field names, that caused a null value for me. Make sure A equals B in the code below. I am guessing the initial fieldName is "Product", so after the transformation, this does not equal "strapi_product" and returns a null value.

const cmsSubschema = await createRemoteSchema({
    url: 'https://schema2.com/graphql/',
    transforms: [
        new RenameRootFields(
            (_, fieldName) => `strapi_${fieldName}`,), // A
        new RenameTypes((name) => `Strapi_${name}`),
    ],
});

// Build the combined schema and set up the extended schema and resolver
const schema = stitchSchemas({
    subschemas: [productsSubschema, cmsSubschema],
    typeDefs: `
    extend type Product {
        cmsMetaData: Strapi_Product
    }
    `,
    resolvers: {
        Product: {
            cmsMetaData: {
                selectionSet: `{ id }`,
                resolve(product, args, context, info) {
                    
                    // Get the data for the extended type from the subschema for Strapi
                    return delegateToSchema({
                        schema: cmsSubschema,
                        operation: 'query',
                        fieldName: 'strapi_product', // B
                        args: { where: { SaleorID: product.id } },
                        context,
                        info,
                    });
                },
            },
        },
    },
});
Related