How to Implement @requires for Computed Fields
When architecting distributed GraphQL APIs, computed fields that depend on cross-subgraph data frequently trigger query resolution bottlenecks. Understanding how to implement @requires for computed fields is critical for maintaining strict service boundaries while enabling seamless data aggregation. This guide details the directive mechanics, resolver wiring, and exact diagnostic workflows required to resolve dependent fields without introducing N+1 queries or circular dependencies. For broader architectural context on entity boundaries and ownership contracts, review the foundational concepts in Subgraph Implementation & Entity Resolution.
Directive Mechanics & Query Planning
The @requires directive signals to the Apollo Gateway that a local field cannot be resolved without first fetching specific fields from another subgraph. When the query planner encounters a computed field, it generates a sequential execution plan:
- Projection Phase: The gateway queries the owning subgraph to fetch the fields declared in
@requires(fields: "..."). - Reference Injection: The fetched values are attached to the entity reference object passed to the dependent subgraph.
- Local Resolution: The dependent resolver extracts the required payload, executes the computation, and returns the final scalar/object.
This contract enforces strict data locality. The gateway automatically handles field projection, ensuring only declared dependencies traverse the network.
Minimal Viable Configuration
Federated SDL
Declare external ownership explicitly. The gateway validates this during composition.
type Product @key(fields: "id") {
id: ID!
price: Float! @external
currency: String! @external
totalPrice: Float! @requires(fields: "price currency")
}
TypeScript Resolver Implementation
The resolver receives the required fields directly on the reference object. Implement synchronous extraction or async computation with defensive null checks.
import { GraphQLResolveInfo } from 'graphql';
interface ProductReference {
__typename: 'Product';
id: string;
price?: number;
currency?: string;
}
export const resolvers = {
Product: {
totalPrice: async (
reference: ProductReference,
_args: Record<string, unknown>,
context: Context,
_info: GraphQLResolveInfo
): Promise<number | null> => {
const { price, currency } = reference;
// Defensive guard against partial entity fetches
if (price == null || currency == null) return null;
const exchangeRate = await fetchExchangeRate(currency, 'USD');
return price * exchangeRate;
}
}
};
Gateway Execution Flow
query GetProductTotal {
product(id: "prod_123") {
id
price
currency
totalPrice
}
}
Planner Output:
InventorySubgraph:query { product(id: "prod_123") { id price currency } }PricingSubgraph:query { _entities(representations: [{__typename: "Product", id: "prod_123", price: 19.99, currency: "EUR"}]) { ... on Product { totalPrice } } }
Diagnostic Workflows & Exact Error Payloads
1. Composition Failure: Missing @external
Error Payload:
[Composition Error] Field "Product.totalPrice" requires "price" but "price" is not declared as @external.
Root Cause: The gateway cannot distinguish between locally owned and externally sourced fields, violating the ownership contract. Resolution Path:
- Add
@externalto every field referenced inside@requires. - Run
rover subgraph checkor@apollo/federationcomposition to verify directive validation passes. - Ensure field names and types exactly match the owning subgraph’s SDL.
2. Runtime Null Injection
Error Payload:
GraphQLError: Cannot return null for non-nullable field Product.totalPrice.
at completeValue (graphql/execution/execute.js:580:13)
at resolveFieldValueOrError (graphql/execution/execute.js:503:18)
Root Cause: The owning subgraph’s resolver dropped the required field, or the reference object was not correctly projected by the gateway. Resolution Path:
- Enable
debug: truein the gateway to log_entitiespayloads. - Verify the owning subgraph returns the exact fields in the
__resolveReferenceor root resolver. - Implement explicit null guards in the dependent resolver (as shown in the minimal config).
3. Incorrect Field Path Syntax
Error Payload:
[Query Planner Warning] Dependency "metadata.taxRate" could not be resolved. Falling back to null.
Root Cause: Dot notation mismatch or referencing a field that doesn’t exist in the composed supergraph schema. Resolution Path:
- Validate paths against
rover supergraph composeoutput. - Use space-separated syntax for top-level fields:
@requires(fields: "price currency"). - For nested objects, ensure the parent type is also extended/externalized if required by the schema hierarchy.
Performance Guardrails
- Batch External Calls: If
@requirespulls in arrays or triggers multiple external API calls, wrap the computation in a DataLoader or equivalent batcher to prevent N+1 latency spikes. - Cache Exchange Rates/Constants: Computed fields that rely on static or slowly changing external data (e.g., tax rates, currency conversions) should cache results at the resolver or service layer.
- Avoid Circular Dependencies: The query planner will fail to generate a plan if Subgraph A requires fields from Subgraph B, which in turn requires fields from Subgraph A. Flatten dependencies or introduce a dedicated aggregation service.
FAQ
Can @requires reference nested fields or arrays?
Yes. Use exact dot notation matching the composed schema (e.g., @requires(fields: "metadata.taxRate")). For arrays, the gateway passes the entire array into the reference object. You must handle iteration and aggregation within the resolver logic.
How does the gateway handle @requires in query planning?
The planner analyzes the query graph, identifies the directive, and generates a sequential execution plan. It first queries the owning subgraph, then passes those values as part of the entity reference to the dependent subgraph. This ensures strict data locality while enabling cross-service computation.
What happens if the external subgraph fails to resolve the required field?
The gateway propagates a partial response. The dependent resolver receives undefined. Implement null-checks and fallback defaults in the resolver to prevent cascading errors, and configure appropriate error boundaries in the gateway to handle partial data gracefully.
Next Steps
- Run composition validation locally before deploying subgraph updates.
- Instrument resolver metrics to track
@requirespayload sizes and latency. - Review query planner traces in Apollo Studio to verify sequential execution matches expected data flow.