Apollo GraphQL-Tools:如何修改默认解析器行为
Great question! You're correct that GraphQL.js falls back to a default resolver for any field without an explicit one — this resolver either returns the matching property on the source object or calls a method of the same name. If you want to replace this default logic with your own custom behavior, there are a few practical approaches depending on how broad you need the change to be:
Approach 1: Override the Global defaultFieldResolver
GraphQL.js exposes the defaultFieldResolver function directly, so you can replace it to change behavior for all fields across all your schemas. This is the most sweeping approach:
const { defaultFieldResolver } = require('graphql'); // Define your custom default resolver logic const myCustomDefaultResolver = (source, args, context, info) => { console.log(`Resolving field: ${info.fieldName}`); // Example: Fallback to a default value if the property doesn't exist const value = source[info.fieldName]; if (typeof value === 'function') { return value(args, context, info); } return value ?? 'N/A'; }; // Replace the global default resolver require('graphql').defaultFieldResolver = myCustomDefaultResolver;
Note: This will affect every GraphQL schema in your application, so use it carefully if you're maintaining multiple schemas with different requirements.
Approach 2: Set Type-Level Default Resolvers with graphql-tools
If you only want to customize behavior for a specific schema or type, you can use the special __resolveField property in your resolver map. This tells graphql-tools to use your custom logic for any field on that type that doesn't have its own explicit resolver:
const { makeExecutableSchema } = require('@graphql-tools/schema'); const typeDefs = ` type User { id: ID! name: String email: String } type Query { user: User } `; const resolvers = { User: { __resolveField: (source, args, context, info) => { // Custom logic: Mask sensitive fields like email if (info.fieldName === 'email') { const [localPart, domain] = source.email.split('@'); return `${localPart.substring(0, 2)}****@${domain}`; } // Fallback to standard behavior for other fields, or add your own logic return source[info.fieldName] ?? 'Not provided'; } }, Query: { user: () => ({ id: '1', name: 'Alice', email: 'alice@example.com' }) } }; const schema = makeExecutableSchema({ typeDefs, resolvers });
Here, any field on the User type without an explicit resolver will use the __resolveField logic, while other types remain unaffected.
Approach 3: Use Schema Directives for Granular Control
For the most precision — only applying custom logic to specific fields or types — you can create a custom schema directive. This lets you tag fields that should use your default resolver behavior:
const { makeExecutableSchema } = require('@graphql-tools/schema'); const { SchemaDirectiveVisitor } = require('@graphql-tools/utils'); const { defaultFieldResolver } = require('graphql'); // Define your custom directive logic class CustomDefaultDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field) { // Preserve the original resolver if one exists const originalResolver = field.resolve || defaultFieldResolver; // Wrap the resolver with your custom logic field.resolve = async (source, args, context, info) => { console.log(`Processing field: ${info.fieldName}`); // Run the original resolver logic const result = await originalResolver(source, args, context, info); // Modify the result as needed (e.g., replace nulls) return result ?? 'Default Value'; }; } } const typeDefs = ` directive @customDefault on FIELD_DEFINITION type User { id: ID! name: String @customDefault email: String @customDefault } type Query { user: User } `; const resolvers = { Query: { user: () => ({ id: '1', name: null, email: 'bob@example.com' }) } }; const schema = makeExecutableSchema({ typeDefs, resolvers, schemaDirectives: { customDefault: CustomDefaultDirective } });
This approach is perfect if you don't want to change behavior globally or per-type, but only for specific fields you explicitly tag.
Quick Recap:
- Global change: Override the built-in
defaultFieldResolver - Per-type change: Use the
__resolveFieldproperty on your type resolver - Per-field control: Create a custom schema directive
内容的提问来源于stack exchange,提问作者Sacha

