graphql-tools actually includes a addSchemaLevelResolveFunction utility that allows you to wrap the resolver for every Query, Mutation and Subscription field to simulate a "root level resolver":
const { makeExecutableSchema, addSchemaLevelResolveFunction } = require('graphql-tools')
const schema = makeExecutableSchema({ resolvers, typeDefs })
const rootLevelResolver = (root, args, context, info) => {
// Your validation logic here. Throwing an error will prevent the wrapped resolver from executing.
// Note: whatever you return here will be passed as the parent value to the wrapped resolver
}
addSchemaLevelResolveFunction(schema, rootLevelResolver)
This is a simple way of applying some logic to all root-level fields, but gets a bit hairy if there's only some fields you want to apply this logic to. If that's the case, now you have to maintain a list of whitelisted or blacklisted fields, separate from your schema. This can be troublesome if someone else on your team is adding a new field and isn't aware of this mechanism. It's also not all-too-helpful if you want to apply the same logic to fields outside of root level ones.
A better approach is to utilize a custom schema directive, as outlined in the docs. This allows you to indicate which fields to apply the logic to:
directive @customValidation on FIELD_DEFINITION
type Query {
someField: String @customValidation
someOtherField: String
}