Join us from October 8-10 in New York City to learn the latest tips, trends, and news about GraphQL Federation and API platform engineering.Join us for GraphQL Summit 2024 in NYC
Docs
Start for Free
Since 2.8

Use contexts to share data

Share data along type hierarchies without overloading @keys


The @context and @fromContext are Enterprise features of the Apollo and require an organization with a GraphOS Enterprise plan. If your organization doesn't have an , you can test it out by signing up for a free Enterprise trial.

In an , a nested object may have a dependency on an ancestor type and thus need access to that ancestor's (s).

To enable a descendant to access an ancestor's field, you could add it as a @key field to every entity along the chain of nested types, but that can become problematic. Deeply nested types can change the @key of many entities. The added field may be irrelevant to the entities it's added to. Most importantly, overloading @key fields often breaks the separation of concerns between different .

The @context and @fromContext directives enable a to share fields without overloading @key fields. You can use these directives to define one or more contexts in a subgraph. Contexts provide a way for a subgraph to share data between types of a nested-type hierarchy without overloading entity keys with extraneous fields. Contexts also preserve the separation of concerns between different subgraphs.

Using one context

As an example of a single context, the subgraph below tracks the last financial transaction made per user. The Transaction type is a child of the User type. Each transaction depends on the associated user's currency to calculate the transaction amount in the user's currency. That dependency—the currencyCode of a Transaction depending on the userCurrency { isoCode } of a User— is defined with a context. The @context on User sets the context, and @fromContext on currencyCode gets the contextual data.

Example: using @context and @fromContext
scalar CurrencyCode;
type Currency {
id: ID!
isoCode: CurrencyCode!
}
type User @key(fields: "id") @context(name: "userContext") {
id: ID!
lastTransaction: Transaction!
userCurrency: Currency!
}
type Transaction @key(fields: "id") {
id: ID!
currency: Currency!
amount: Int!
amountInUserCurrency(
currencyCode: CurrencyCode
@fromContext(field: "$userContext { userCurrency { isoCode } }")
): Int!
}

NOTE

  • Context names cannot include underscores.
    • In the example above, userContext is a valid context name, but user_context wouldn't be.
  • An argument of @fromContext doesn't appear in the . Instead, it's populated automatically by the .
    • In the example above, the argument currencyCode: CurrencyCode! wouldn't appear in the API schema.

Using type conditions in @fromContext

In this example, note how the @fromContext directive uses a series of type condition to select the desired field when accessing Child.prop1. A type condition is not required if all possible contexts have a field present as is the case for Child.prop2.

Example: using multiple contexts
type Query {
a: A!
b: B!
c: C!
}
type A @key(fields: "id") @context(name: "context1"){
id: ID!
field: String!
someField: String!
child: Child!
}
type B @key(fields: "id") @context(name: "context1"){
id: ID!
field: String!
someField: String!
child: Child!
}
type C @key(fields: "id") @context(name: "context1") {
id: ID!
field: String!
someOtherField: String!
child: Child!
}
type Child @key(fields: "id") {
id: ID!
prop1(
arg: String!
@fromContext(field: "$context1 ... on A { someField } ... on B { someField } ... on C { someOtherField }")
): Int!
prop2(
arg: String!
@fromContext(field: "$context1 { field }")
): Int!
}

When the same contextual value is set in multiple places—as in the example with the Child.prop1 and Child.prop2 args—the FieldValue must resolve all types from each place into a single value that matches the parameter type.

NOTE

Federation doesn't guarantee which context will be used if a field is reachable via multiple contexts.

Disambiguating contexts

When multiple ancestor entities in the type hierarchy could fulfill a set context, the nearest ancestor is chosen. For example, if both the parent and grandparent of a type can provide the value of a context, the parent is chosen because it's the closer ancestor.

In the following example, given nested types A, B, and C, with C referencing a context that either A or B could provide, C uses the value from B because it's a closer ancestor to C than A:

type Query {
a: A!
}
type A @key(fields: "id") @context(name: "context1") {
id: ID!
field: String!
b: B!
}
type B @key(fields: "id") @context(name: "context1") {
id: ID!
field: String!
c: C!
}
type C @key(fields: "id") {
id: ID!
prop(
arg: String! @fromContext(field: "$context1 { field }")
): Int!
}

In a more complex , a field could be reachable via multiple paths, and a different field could be used to resolve the prop depending on which path was used.

Referencing fields across subgraphs

The definition of context scopes can only exist in one . The @fromContext directive can't reference a @context defined in another subgraph. However, you can use contexts to share data across subgraphs using the @external reference.

Reusing the Transaction example, imagine a subgraph responsible for the User and Currency types:

scalar CurrencyCode
type Currency @shareable {
id: ID!
isoCode: CurrencyCode!
}
type User @key(fields: "id") {
id: ID!
userCurrency: Currency!
}

If you want to reference those fields from another subgraph, you can use the @external directive to pass data across subgraph boundaries:

scalar CurrencyCode
type Currency @shareable {
id: ID!
isoCode: CurrencyCode!
}
type User @key(fields: "id") @context(name: "userContext") {
id: ID!
# This is a reference to the field resolved elsewhere
userCurrency: Currency! @external
# We add this field to our type here
lastTransaction: Transaction!
}
type Transaction @key(fields: "id") {
id: ID!
currency: Currency!
amount: Int!
amountInUserCurrency(
currencyCode: CurrencyCode
@fromContext(field: "$userContext { userCurrency { isoCode } }")
): Int!
}
Previous
Resolve Another Subgraph's Fields
Next
Entity Interfaces
Rate articleRateEdit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc., d/b/a Apollo GraphQL.

Privacy Policy

Company