Last active
October 19, 2021 12:46
-
-
Save adamkl/2a0f89dcd5ab9b86f74d899e8cfa5c18 to your computer and use it in GitHub Desktop.
An example of how to chain together local and remote GraphQL resolvers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!" | |
} | |
} | |
} | |
}; |
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
.
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
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
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]
+-- @types/[email protected]
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!