Use a GraphQL Schema Directive to define Javascript logic, have that logic executed as a resolver.
Its a directive you can use on Fields:
directive @code(source: String!) on FIELD_DEFINITION
You define Javascript logic in the source
argument. The source is wrapped in an IFFE & passed into a https://nodejs.org/api/vm.html. Supports Promises & you can use dependency injection via the context.
$ npm install graphql-code-directive
const { ApolloServer } = require("apollo-server");
const codeDirective = require("graphql-code-directive");
const fetch = require("node-fetch");
const typeDefs = `
type Todo {
id: ID!
userId: ID!
title: String!
completed: Boolean
}
type Query {
todos: [Todo]
@code(
source: """
const response = await context.fetch('https://jsonplaceholder.typicode.com/todos');
return response.json()
"""
)
}
`;
const server = new ApolloServer({
typeDefs: [codeDirective.typeDefs, typeDefs],
schemaDirectives: {
code: codeDirective.CodeDirective,
},
context: () => {
return {
fetch,
};
},
});
server.listen(4000).then(() => console.log("http://localhost:4000"));
Inside the code source you have access to the four global variables:
rootValue
args
context
resolveInfo
So for example if you were to write a 'Normal' Javascript resolver the variables would map to each argument:
function myResolver(rootValue, args, context, resolveInfo) {}
Make sure you disable introspection & yes it is safe. You are in control of what the VM has access to, via context
, and the Node.js team has done a good job at isolating the VM.
Unit testing could become cumbersome, this is because you would have to parse the definitions into an AST in order to access the source
. You can however write solid integration tests, why not checkout https://www.apollographql.com/docs/apollo-server/testing/testing/ ?
Yes! There are 3 exports:
typeDefs
- A string of the directives type definitionsCodeDirective
- A legacySchemaDirectiveVisitor
that you are most likely to be familiar withcodeDirective
- A functional approach to Schema Directives that returns a transformer
const { codeDirective } = require("graphql-code-directive");
const { makeExecutableSchema } = require("@graphql-tools/schema");
const { codeDirectiveTypeDefs, codeDirectiveTransformer } = codeDirective();
let schema = makeExecutableSchema({
typeDefs: [codeDirectiveTypeDefs, typeDefs],
resolvers: {},
});
schema = codeDirectiveTransformer(schema);
MIT licence.