I have a node.js project powered by apollo-server. I use custom @admin
directive that does permission checking on queries, mutations and object fields. For queries and mutation this directive throws errors, for fields it returns null instead of real value.
Now, I want to add graphiql ui to my project, so fellow developers can explore my graphql schema. However, I want them to see the schema as anonymous user sees it, i.e. they should not know about existence of @admin
fields and @admin
queries and all mutations (even non-admin ones). Even those who have credentials to execute these operations (i.e. logged in as admin) should not see those parts of schema.
As far as I understand, graphiql sends special introspection query, which contains __schema
and __type
fields to display schema and its documentation.
Is it possible to somehow modify my schema, which is constructed using makeExecutableSchema
from graphql-tools
to achieve my goal?
You can use an extended schema for the main endpoint and use the same schema without the extension for the graphiql endpoint.
Let's take this schema definition as an example:
// schemaDef
type Query {
anonQuery: QueryResult
adminQuery: AdminQueryResult @admin
}
And the executable schema:
const schema = makeExecutableSchema({
typeDefs: [schemaDef /* ... additional schema files */],
resolvers: merge(schemaResolvers/* ... additional resolvers */)
})
Now, let's split the schema definitions with the help of the extend
keyword.
Read here about Extending Types and the extend
keyword.
// anonymous part of the original schema definition:
type Query {
anonQuery: QueryResult
}
// admin extensions definitions:
extend type Query {
adminQuery: AdminQueryResult @admin
}
To avoid having some warnings about resolvers not defined in the schema, you'll probably want to split the admin related resolvers to another file or another resolver map.
Now you'll have 2 executable schemas:
const mainSchema = makeExecutableSchema({
typeDefs: [schemaDef /* ... additional schema files */],
resolvers: merge(schemaResolvers/* ... additional resolvers */)
})
const extendedSchema = makeExecutableSchema({
typeDefs: [schemaDef, adminSchemaExtensions /* ... additional schema files */],
resolvers: merge(schemaResolvers, adminSchemaResolvers /* ... additional resolvers */)
})
Your main endpoint should use the extended schema.
router.use('/graphql', /* some middlewares */, graphqlExpress({schema: extendedSchema}))
Since the GraphiQL endpoint expects a GraphQL endpoint, you'll have to create another one specifically for the second schema. Perhaps something like this:
router.use('/graphql-anon', /* some middlewares */, graphqlExpress({schema: mainSchema}))
router.use('/graphiql', /* some middlewares */, graphiqlExpress({endpointURL: '/graphql-anon'}))
That's it!
Now most of your code is shared and only part of the schema is accessible using the GraphiQL interface.
Putting the admin definitions in separate files could be seen as more convenient or less convenient, depending on your project, code and preferences.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With