Skip to content

Instantly share code, notes, and snippets.

@adamkl
Last active October 19, 2021 12:46
Show Gist options
  • Save adamkl/2a0f89dcd5ab9b86f74d899e8cfa5c18 to your computer and use it in GitHub Desktop.
Save adamkl/2a0f89dcd5ab9b86f74d899e8cfa5c18 to your computer and use it in GitHub Desktop.
An example of how to chain together local and remote GraphQL resolvers.
import {
fetchRemoteSchema,
authLink
} from "@private/graphql-proxy";
import { startGraphQLService } from "@private/graphql-scripts";
import { graphql, printSchema, print, parse } from "graphql";
import {
mergeSchemas,
makeRemoteExecutableSchema,
makeExecutableSchema
} from "graphql-tools";
import * as graphqlMask from "graphql-mask";
import { ApolloLink } from "apollo-link";
import { getAppSvcInstance } from "./appSvcInstance";
import { localExtensions } from "./graphql/schema";
import { localResolvers } from "./graphql/resolvers";
// This function just automates the fetching of a remote schema
fetchRemoteSchema({
name: "remote GraphQL API",
host: "remote.example.com",
path: "/graphql",
port: 443,
ssl: true,
introspectionToken: "abc"
}).then((remoteSchema, httpLink) => {
// *** Important section below ***
// Merge remote schema with local extensions
const mergedSchema = mergeSchemas({
schemas: [remoteSchema, localExtensions]
});
// Make a locally executable version of the merged schema
const localExecutableSchema = makeExecutableSchema({
typeDefs: printSchema(mergedSchema),
resolvers: localResolvers
});
// Create a post processing link using ApolloLink
// Takes data retrieved from remote API and reprocesses with with graphql
// and the local schema
const localProcessingLink = new ApolloLink((operation, forward) => {
// Save the incoming query to use locally
const localQuery = operation.query;
// Filter out any local extensions to keep them from being
// sent to remote API
operation.query = parse(
graphqlMask(remoteSchema, print(operation.query))
);
return forward!(operation).map(data => {
// Take the remote data and reprocess it locally
return graphql(
localExecutableSchema,
print(localQuery),
data.data,
operation.getContext().graphqlContext,
operation.variables,
operation.operationName
);
});
});
// Wire everything up
const remoteExecutableSchema = {
name: "remote GraphQL API",
schema: makeRemoteExecutableSchema({
schema: localExecutableSchema,
link: ApolloLink.from([localProcessingLink, authLink, httpLink])
})
};
// *** end of important section ***
// Start up the proxying server
// (this function essentially just passes the schema into express-graphql)
startGraphQLService({
schema: remoteExecutableSchema.schema,
appSvcFactory: getAppSvcInstance
});
});
// ./graphql/schema.ts
export const localExtensions = `
extend type Customer {
greeting: String
}`;
// ./graphql/resolvers.ts
export const localResolvers = {
Query: {
customer: {
// Let's change the customer's name as we proxy the data through
resolve: (parent, args, context, info) => {
const { customer } = parent;
return {
...customer,
name: `Mr. ${customer.name}`
}
}
}
},
Customer: {
greeting: {
// Let's extend the Customer with a greeting
resolve: (parent, args, context, info) => {
return "Hello!"
}
}
}
};
@marticrespi
Copy link

Hi, thanks to share your example!

But I have a problem with this code:

Argument of type '(operation: Operation, forward: NextLink) => Observable<Promise<ExecutionResult<ExecutionResultDataDefault>>>' is not assignable to parameter of type 'RequestHandler'. Type 'Observable<Promise<ExecutionResult<ExecutionResultDataDefault>>>' is not assignable to type 'Observable<FetchResult<Record<string, any>, Record<string, any>>>'. Type 'Promise<ExecutionResult<ExecutionResultDataDefault>>' has no properties in common with type 'FetchResult<Record<string, any>, Record<string, any>>'.

Here is my package.json

+-- apollo-link-http@1.5.5
+-- apollo-server@2.2.1
+-- apollo-server-express@2.2.1
+-- cors@2.8.5
+-- express@4.16.4
+-- fetch-graphql-schema@0.2.1
+-- graphql@14.0.2
+-- graphql-mask@0.1.2
+-- graphql-tools@4.0.3
+-- node-fetch@2.3.0
+-- request@2.88.0
+-- rxjs@6.3.3
+-- typescript@3.0.3
+-- @types/graphql@14.0.3

I thought I should return an observable inside apolloLink function, I have tried to change it and returning an observable ( with the from function from rxjs) and it doesn't fix the problem. I think the problem is that there are two returns and it's concatenating 2 observables, one inside the other one.

How we can solve it?

Many thanks!

@dimitar-nikovski
Copy link

dimitar-nikovski commented Mar 7, 2019

Firstly many thanks for this gist @adamkl!

Anyone trying this, you will need

// Filter out any local extensions to keep them from being
    // sent to remote API
    operation.query = parse(
      graphqlMask(remoteSchema, print(operation.query)).maskedQuery // *** notice here
    );

for the latest version of graphql-mask to work with parse.

@iDVB
Copy link

iDVB commented Oct 19, 2021

Thanks @adamkl !
Any idea how relevant the code is now that it seems they are moving aways from using ApolloLink for this and instead using executors? I may not understand this but reading up on ApolloLink is looks as though they moved it into apollo-client and are leading people towards executors ?

Also, I'd LOVE to see what some of you custom functions do to help complete the picture. fetchRemoteSchema, authLink etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment