6

My plight began as a simple desire to expand my graphql schema from a single .graphql file to multiple files so i can better organize the schema and so it wouldn;t grow to one huge file out of control.

My original layout was very straight forward and i had a working schema in a schema.graphql file. I would be able to parse it into a string using importSchema('server/schema.graphql') from the graphql-import library, which is now deprecated https://github.com/ardatan/graphql-import

They mention that it has been merged into graphql-tools in the newest version and provide a migration tutorial here https://www.graphql-tools.com/docs/migration-from-import The tutorial seems very straight forward since their first example pretty much illustrate exactly what my code looks like (except i dont use es6 import but old fashoined require):

import { importSchema } from 'graphql-import';
import { makeExecutableSchema } from 'graphql-tools';

const typeDefs = importSchema(join(__dirname, 'schema.graphql'));
const resolvers = {
  Query: {...}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });

And then they say to modify it, simply make these changes

import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema';

const schema = loadSchemaSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = { Query: {...} };

const schemaWithResolvers = addResolversToSchema({
  schema,
  resolvers,
});

I made those changes but the vital difference is that they no longer use makeExecutableSchema() in their example, which is pretty important for me since i need to include the directives. What do i do now with the schema? How do i declare the directives? their documentation for directives still uses makeExecutableSchema but i cant use it anymore since the new loadSchemaSync function returns an object instead of a string literal which i would need to pass to typeDefs in makeExecutableSchema

I am using apollo-server, so it seemed a possible workaround was to just declare the directives in the apollo-server constructor and just pass in this new schemaWithResolvers as a schema as such

const server = new ApolloServer({
    schema, //this includes now the returned value of using addResolversToSchema()
    schemaDirectives : {
        auth:AuthDirective,
        authRole: AuthRoleDirective
    }
    context : ({req}) => //dostuff,

});

This allows my server to run, and i can perform queries and mutations, however, my directives are no longer working, and i no longer have authentication on protected queries.

I would like a way to import my .graphql file and parse it into a string so i can use it inside typeDefs as i used to with importSchema() or a way to declase my directies without using makeExecutableSchema() so that they continue working again!

I have gone up and down the documentation and seen other libraries and so far i keep coming up short, any tips or guidance is greatly appreciated

xunux
  • 1,531
  • 5
  • 20
  • 33

3 Answers3

4

makeExecutableSchema is still part of graphql-tools and you can continue to use it as shown here in the docs. The issue with the example shown in the docs is that it's not equivalent to what you were doing before. You should use loadTypedefsSync instead:

import { loadTypedefsSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema';

const sources = loadTypedefsSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const documentNodes = sources.map(source => source.document);
const resolvers = { Query: {...} };

const schema = makeExecutableSchema({ typeDefs, resolvers });

Alternatively, if you go the loadSchema route, you should be able to apply the directives to your schema after loading it:

import { SchemaDirectiveVisitor } from "@graphql-tools/utils";
import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema';

const schema = loadSchemaSync(join(__dirname, 'schema.graphql'), { loaders: [new GraphQLFileLoader()] });
const resolvers = { Query: {...} };

const schemaWithResolvers = addResolversToSchema({
  schema,
  resolvers,
});

SchemaDirectiveVisitor.visitSchemaDirectives(schemaWithResolvers, schemaDirectives);
Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183
  • i cannot find loadTypedefsSync in the documentation anywhere, so not sure how you know about it but thanks! this solution worked for me! – xunux Jun 18 '20 at 21:52
  • 1
    So both solutions worked for me, so thanks, however, the first solution using loadTypedefsSync did not allow me to import multiple .graphql files and use the #import feature within the files, but the second approach did. Do you have an approach for using your first solution but still being able to import multiple `.graphql` files and use the #import feature within the files? – xunux Jun 18 '20 at 23:31
  • `loadSchema` uses `loadTypedefs` under the hood, so I would expect the behavior to be same with regard to the import syntax. You may want to open an issue against the repo with that question – Daniel Rearden Jun 18 '20 at 23:36
  • how do you find out about these functions? loadTypedefs i cannot find in the documentation for graphql-tools and the repo for graphql-tools/load gave me a 404, do you straight up look at the source code? – xunux Jun 19 '20 at 01:44
  • 2
    If anyone tripped up trying solution #1 going from documentNodes to typeDefs, you need to import { concatAST } from "graphql" and then typeDefs = concatAST(documentNodes) – G Gallegos Jun 21 '20 at 03:56
  • So far I haven't used _makeExcecutableSchema_ since it seems I could avoid it. Here's my tweak to get Apollo running: **typeDefs = sources.map(source => source.document); new ApolloServer({ typeDefs, resolvers, ... })** – lapponiandevil Jun 23 '20 at 16:40
  • `documentNodes` in the first solution should be `typeDefs` – J. Titus Jan 28 '21 at 23:35
  • When using the second approach, how do I create schemaDirectives var from a DocumentNode object containing the directive declarations? – Dmitry Klochkov Jun 20 '21 at 07:48
  • In the first example you aren't even including the schema directives. – Benjamin R Feb 04 '22 at 08:41
2

I tried this way but I couldn't solve the problem. A unique solution that managed to take the following approach:

const { ApolloServer, makeExecutableSchema, gql} = require('apollo-server-express')
const { loadTypedefsSync }  = require('@graphql-tools/load')
const { GraphQLFileLoader } = require('@graphql-tools/graphql-file-loader')
const path = require('path')

const sources = loadTypedefsSync(
    path.resolve(__dirname, '../schema/root.graphql'),
    { loaders: [new GraphQLFileLoader()] }
)
const typeDefs = sources.map(source => source.document)
const schema = makeExecutableSchema({
    typeDefs: gql`${typeDefs[0]}`,
    resolvers,
})
0

I had the same issue I loaded the Schema via .graphql and I want to add the graphql-constraint-directive. My Solution was to load the schema with loadSchemaSync and then to use the wrapSchema to use the transform functions, you need also to add the directives into one of your .graphql files:

import { addResolversToSchema, wrapSchema } from 'graphql-tools';
import { GraphQLSchema } from 'graphql';
import resolvers from './resolver';

schema = loadSchemaSync('./**/*.graphql', {
  loaders: [new GraphQLFileLoader()],
});

const schemaWithResolver = addResolversToSchema({
    schema,
    resolvers
  });

const { constraintDirective } = require('graphql-constraint-directive')
  
const schemaConstrain = wrapSchema({
  schema: schemaWithResolver,
  transforms: [constraintDirective()]
})

Documentation to Schema Wrapping

Rafael
  • 1
  • 1
    You don't need to use wrapSchema. It is not ideal. It adds an additional delegation step which might have performance implications please follow graphql-constraint-directive docs for this. We don't use wrapSchema in any of examples about directives in GraphQL Tools documentation. – ardatan Dec 29 '21 at 14:53