Skip to content

Feature Request: Add response context support for interceptors (esp. Subscriptions) #3863

@michelepra

Description

@michelepra

Problem

Interceptors in gqlgen only receive the request context as their first parameter.
For queries and mutations, this works fine because execution is linear:

  1. Receive request
  2. Execute resolver
  3. Return response (context == request context)

For subscriptions, however:

  • gqlgen starts a goroutine that publishes values to a channel
  • gqlgen reads from this channel and encodes responses
  • At this point, the response has no associated context that interceptors can access

As a result, interceptors cannot access or enrich the "response context" (e.g., metadata, tracing, request-scoped values) for subscription payloads.
They only see the request context from when the subscription was created.


Proposed Changes

1. Extend Response struct with a Context field

Current:

type Response struct {
	Errors     gqlerror.List   `json:"errors,omitempty"`
	Data       json.RawMessage `json:"data"`
	Label      string          `json:"label,omitempty"`
	Path       ast.Path        `json:"path,omitempty"`
	HasNext    *bool           `json:"hasNext,omitempty"`
	Extensions map[string]any  `json:"extensions,omitempty"`
}

Proposed:

type Response struct {
	Context context.Context `json:"-"`

	Errors     gqlerror.List   `json:"errors,omitempty"`
	Data       json.RawMessage `json:"data"`
	Label      string          `json:"label,omitempty"`
	Path       ast.Path        `json:"path,omitempty"`
	HasNext    *bool           `json:"hasNext,omitempty"`
	Extensions map[string]any  `json:"extensions,omitempty"`
}

This allows interceptors to access response-level context.

2. Introduce a SubscriptionField interface for subscription result payloads

Instead of subscription resolvers returning (<-chan *T, error), have them return something like:

type SubscriptionField[T any] interface {
	GetContext() context.Context
	GetField() T
}

So subscription resolvers could return:

(<-chan SubscriptionField[*T], error)

This lets each published item carry its own context, which flows naturally into the modified Response.Context.


Some benefit

  • Consistent interceptor behavior across queries, mutations, and subscriptions withouth break current interceptor code
  • Enables request/response tracing, per-event metadata, and richer observability for subscriptions
  • Clean separation of concerns: business payload stays in GetField(), while per-response metadata lives in context.Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions