Skip to content

Context

context is a optional argument that our fields can retrieve. It lets fields access global data, like database connections.

# Define our own context type
class MyContext < GraphQL::Context
  @pi : Float64
  def initialize(@pi)
  end
end

# Pass it to schema.execute
context = MyContext.new(Math::PI)
schema.execute(query, variables, operation_name, context)

# Access it in our fields
@[GraphQL::Object]
class MyMath < GraphQL::BaseObject
  @[GraphQL::Field]
  def pi(context : MyContext) : Float64
    context.pi
  end
end

Context instances must not be reused for multiple executions. A subscription is one execution: the context passed to schema.subscribe is kept for the lifetime of the subscription and reused for every event it delivers, and the WebSocket transport builds one context per operation on a connection. A resolver that reads per-request state from the context therefore sees the same values for as long as the subscription runs.

Handling exceptions

When a resolver raises, the context decides what happens. handle_exception receives the exception and returns the message to put in the response's errors; the default returns ex.message. Return nil to report nothing, or raise to let the exception bubble out of schema.execute:

class MyContext < GraphQL::Context
  def handle_exception(ex : ::Exception) : String?
    ex.is_a?(NotFound) ? "not found" : raise ex
  end
end

How a failed field appears in the response is described under Errors.

Concurrency

By default, a query is resolved sequentially in the fiber that called schema.execute. To resolve fields and array elements concurrently, set max_concurrency on the context:

context = MyContext.new(Math::PI)
context.max_concurrency = 8
schema.execute(query, variables, operation_name, context)

This is the maximum number of fibers a single execution may have running at once. When the budget is used up, remaining work runs inline, so a large list never fans out into an unbounded number of fibers. Choose a value your downstream resources can sustain; resolvers that check out database connections should stay below the connection pool size.

The root fields of a mutation are always resolved one after another, as the specification requires; fields nested below them still run concurrently.

Complexity

To reject operations that select too many fields, set max_complexity on the context:

context = MyContext.new(Math::PI)
context.max_complexity = 200
schema.execute(query, variables, operation_name, context)

Complexity is the number of fields the operation selects, counted across the whole selection tree with fragments expanded. It is computed before any resolver runs, and an operation over the limit is answered with an error and no data. After execution, context.complexity holds the count.

Depth

Nesting depth is limited separately. Selection sets, lists and input objects may nest max_depth levels, 100 by default, and a deeper query is rejected while parsing. Real queries rarely pass twenty levels; the limit exists so a hostile query cannot exhaust the stack:

context.max_depth = 30