Skip to main content

Introduction

A field resolver turns a GraphQL field into a remote function (RPC). When a query requests that field, the Cosmo Router calls an additional RPC endpoint within your gRPC service to compute its value. The function receives:
  • Parent object context — data from the parent type needed to resolve the field (e.g. the entity’s id)
  • Field arguments (optional) — any arguments declared on the field in the GraphQL schema
Field resolvers are useful whenever a field is expensive to compute, depends on external data, or needs custom business logic — whether or not it accepts arguments.

How It Works

To create a field resolver, annotate a field with the @connect__fieldResolver directive and specify which parent fields to pass as context:
type User {
  id: ID!
  name: String!
  avatar: String! @connect__fieldResolver(context: "id")
}
The context parameter accepts a space-separated list of field names from the parent type. You can pass multiple fields — for example context: "id name" — and only those fields will appear in the generated context message. Cosmo generates a dedicated RPC for the annotated field. The generated request message always contains a repeated context field — one entry per entity in the batch. If the field declares arguments, the request also contains a field_args message.
The context parameter on @connect__fieldResolver is always required. It specifies which parent fields are passed to the resolver.

Runtime Flow

  1. Query Planning — The router identifies fields that require custom resolution
  2. Data Retrieval — Parent data needed for context is fetched first
  3. Field Resolution — The router calls the generated RPC with batched context (and arguments, if any)
  4. Response Assembly — Resolved values are merged back into the GraphQL response

Schema & Generated Protobuf

Field Without Arguments

The simplest case: a field with no arguments, resolved via context alone.
type User {
  id: ID!
  name: String!
  avatar: String! @connect__fieldResolver(context: "id")
}
The request contains only context — one entry per entity in the batch.

Field With Arguments

When a field declares arguments, Cosmo additionally generates an Args message and a field_args field on the request.
type Query {
  foo(id: ID!): Foo!
}

type Foo {
  id: ID!
  bar(baz: String!): String! @connect__fieldResolver(context: "id")
}
Note the addition of ResolveFooBarArgs and the field_args field on the request — this is the only structural difference from the no-arguments case.

Multiple Context Fields

You can select multiple parent fields by listing them space-separated in the context parameter. Only the fields you select are included in the generated context message — no other fields from the parent type are passed to the resolver.
type Product {
  id: ID!
  name: String!
  category: String!
  discount: Float! @connect__fieldResolver(context: "id name category")
}
The ResolveProductDiscountContext message contains exactly the three fields listed in context: "id name category". Other fields on Product (or fields added later) are not included.

Implementation Guide

Step-by-Step Setup

1

Define Schema

Define the schema with field resolvers.
2

Generate Protobuf

Use the wgc grpc-service generate command to generate the protobuf definition for the gRPC service.
3

Implement Resolver Logic

Implement the field resolver logic in the gRPC service.
4

Deploy and Test

Deploy the gRPC service and test the field resolvers.

Example 1: Field Resolver Without Arguments

This example resolves the avatar field by looking up an avatar URL based on the user’s ID from context.
type User {
  id: ID!
  name: String!
  avatar: String! @connect__fieldResolver(context: "id")
}

Example 2: Field Resolver With Arguments

This example retrieves the popularity score for a category based on the ID and a given threshold argument.
type Category {
  id: ID!
  name: String!
  kind: CategoryKind!
  popularityScore(threshold: Int): Int @connect__fieldResolver(context: "id")
}

Data Loading and Batching

Cosmo Connect automatically optimizes field resolver performance through intelligent batching mechanisms that eliminate the N+1 query problem commonly found in GraphQL implementations.

How Batching Works

When your GraphQL operation requests fields across multiple entities, Cosmo Connect:
  1. Aggregates Context: Collects all context elements from the original operation that require field resolution
  2. Batches Requests: Groups multiple field resolution calls into a single gRPC request
  3. Preserves Order: Maintains the original order of context elements to ensure correct response mapping

Implementation Requirements

As a field resolver implementer, you only need to follow one simple rule:
Always return results in the exact same order as the provided context elements. The router relies on positional mapping to correctly associate resolved values with their corresponding entities.
// Good: Maintains positional mapping by returning empty results for skipped elements
func (s *ExampleService) ResolveExample(_ context.Context, req *examplev1.ResolveExampleRequest) (*examplev1.ResolveExampleResponse, error) {
	results := make([]*examplev1.ResolveExampleResult, 0, len(req.GetContext()))
	for _, reqContext := range req.GetContext() {
		if reqContext.GetId() == "1" {
			// Skip resolution for this context element by returning an empty result.
			// This maintains the required 1:1 mapping between context and results.
			results = append(results, &examplev1.ResolveExampleResult{})
			continue
		}

		results = append(results, &examplev1.ResolveExampleResult{
			Example: "example",
		})
	}

	resp := &examplev1.ResolveExampleResponse{
		Result: results,
	}

	return resp, nil
}

// Bad: Breaks positional mapping by skipping result elements
func (s *ExampleService) ResolveExample(_ context.Context, req *examplev1.ResolveExampleRequest) (*examplev1.ResolveExampleResponse, error) {
	results := make([]*examplev1.ResolveExampleResult, 0, len(req.GetContext()))
	for _, reqContext := range req.GetContext() {
		if reqContext.GetId() == "1" {
			// ERROR: Skipping this element without adding a corresponding result
			// will break the positional mapping between context and results.
			continue
		}

		results = append(results, &examplev1.ResolveExampleResult{
			Example: "example",
		})
	}

	resp := &examplev1.ResolveExampleResponse{
		Result: results,
	}

	return resp, nil
}

Entity Resolution

The same batching principles apply to entity lookups in federated scenarios. Whether resolving computed fields or fetching related entities, Cosmo Connect ensures optimal request patterns by aggregating multiple lookups into efficient batch operations.

Performance Considerations

Field resolvers introduce additional execution complexity since they require context-aware resolution across your gRPC services. Understanding the performance implications helps you design efficient resolver implementations.

Execution Flow Impact

When field resolvers are involved in your GraphQL operation:
  1. Context Preparation: The router must first gather all necessary parent data from relevant services
  2. Field Resolution: Additional gRPC calls are made to resolve computed fields with the prepared context
  3. Response Assembly: Resolved field values are merged back into the final GraphQL response

Built-in Optimizations

Cosmo Connect includes several performance optimizations out of the box:
  • Automatic Batching: Multiple field resolutions are automatically grouped into single gRPC calls
  • Parallel Execution: Independent field resolvers can execute concurrently where possible
  • Context Reuse: Shared parent data is fetched once and reused across multiple field resolvers
  • Lazy Loading: Field resolvers are only invoked when their fields are actually requested in the operation

Best Practices

To maximize performance in your field resolver implementations:
  • Minimize External Calls: Reduce dependencies on external services within resolver logic
  • Leverage Batching: Design your resolver to efficiently handle batch requests rather than individual items
  • Cache Strategically: Implement appropriate caching for frequently computed or slowly changing data
  • Monitor Execution: Use Cosmo’s observability features to identify performance bottlenecks

Limitations and Constraints

  • Grouping of multiple field resolvers within a type into a single call is not yet supported.
See also: gRPC Services · Plugins · GraphQL Support