Mapping GraphQL Responses

Mapping HTTP responses to GraphQL fields and transforming values


Apollo Connectors enables you to map HTTP response data from REST APIs to your GraphQL schema. This mapping capability is what makes Connectors powerful, allowing your REST data to be accessed through GraphQL's flexible query language without writing custom transformation code.

In this guide, you'll learn more about:

  • Why Connectors require mapping

  • Rules for mapping HTTP responses

tip
Check out the Connectors Mapping Playground to experiment with and troubleshoot mapping expressions. If you learn best with videos and exercises, this interactive course teaches the syntax and methods of the Connectors mapping language.

Mapping overview

Mapping HTTP responses to your GraphQL schema transforms the data returned by your REST APIs into a format that fits your GraphQL API structure. This ensures GraphQL requests can provide consistent, predictable responses to clients, even when working with data from different API sources.

You map your GraphQL schema to an HTTP response using the Apollo Connectors mapping language in the @connect directive's selection field. For that reason, this process is sometimes referred to as selection mapping.

The mapping language you use for selection mapping is the same mapping language you use in URIs, headers, and request bodies when making HTTP requests.

Unique features of selection mapping

In the context of selection mapping, the mapping language has an important, unique feature. When used in selection, it's assumed that all fields come from the HTTP response body unless otherwise specified. For example, given the following JSON response:

JSON
JSON Response
{
 "id": 1,
 "name": "Lunar Rover Wheels"
}

You can use the following selection to map the id and name fields:

GraphQL
Example Connector with selection
type Query {
  products: Products
    @connect(
      source: "ecomm"
      http: { GET: "/products" }
      selection: "id name"
    )
}

type Product {
  id: ID!
  name: String
}

Multiline syntax

Long selection strings can be broken up into multiple lines with GraphQL multiline string syntax ("""):

GraphQL
Example: Multiline selection
type Query {
  products: Products
    @connect(
      source: "ecomm"
      http: { GET: "/products" }
      selection: """
      id
      name
      description
      """
    )
}

type Product {
  id: ID!
  name: String
  description: String
}

This is particularly valuable when you have a longer nested selection to map. The following example shows how each line in the selection translates a JSON response to fields in the GraphQL schema below.

/products/1
JSON
JSON Response
{
  "id": 1,
  "name": "Lunar Rover Wheels",
  "variants": [
    {
      "name": "Standard Wheel",
      "price": {
        "original": 4999,
        "discounts": [],
        "final": 4999
      },
      "specifications": {
        "Material": {
          "value": "Titanium alloy"
        },
        "Diameter": {
          "value": "50 cm"
        }
      },
      "inventory": {
        "quantity": 100,
        "sellUnavailable": false
      },
      "shipping": {
        "ship1": {
          "weight": 5,
          "method": "GROUND",
          "estimate": {
            "price": 499,
            "arrival": 1675804800000
          }
        },
        "ship2": {
          "weight": 5,
          "method": "AIR",
          "estimate": {
            "price": 999,
            "arrival": 1675790400000
          }
        }
      },
      "upc": "0001234567890",
      "sku": "RW-001",
      "taxable": true,
      "variantId": "variant1"
    }
  ]
}
GraphQL
Multiline nested selection
type Query {
  product(id: ID!): Product
    @connect(
      http: { GET: "https://ecommerce.demo-api.apollo.dev/products/{$args.id}" }
      selection: """
      id                   # 1
      variants {           # 2
        name               # 3
        price {            # 4             
          original         # 5           
          final            # 6
        }        
      }
      """
    )
}

type Product {
  id: ID!                 # 1
  variants: [Variant]     # 2
}

type Variant {
  name: String           # 3
  price: Price           # 4
}

type Price {
  original: Int          # 5
  final: Int             # 6
}

$status variable

In selection, you also get access to the $status variable, which isn't available anywhere else. $status represents the HTTP status code of a response.

Selection mapping rules

The selection field is responsible for more than just mapping response fields to the schema; it powers the core of each Connector, so it has some special rules.

Selections can't be empty

The selection field isn't allowed to be empty. You must map at least one field in every Connector. If you have an endpoint that doesn't return any response data, you can map a scalar value using a literal value:

connectors
success: $(true)

All schema fields must be mapped

The only way to populate a field from a Connector is via selection, so every field defined in the schema must be mapped at least once in a Connector. The exception is fields that are resolved from another subgraph, such as those marked @external.

Leaf selections must be scalars

Different Connectors can resolve different fields of the same object, so you must specify every field that a given Connector resolves. That means you can never map an entire object and expect the fields to be implicitly mapped. You must map all fields explicitly.

GraphQL
type Query {
  product(id: ID!): Product
    @connect(
      source: "ecomm"
      http: { GET: "/products/{$args.id}" }
      selection: "id name description"
    )
}

type Product {
  id: ID!
  name: String!
  description: String
  reviews: [Review]
    @connect(
      source: "ecomm"
      http: {GET: "/products/{$this.id}/reviews"}
      # selection: "$.reviews"  ❌ This won't work
      selection: """          # ✅ This works
      $.reviews {
        id
        rating
        comment
      }
      """
    )
}

type Review {
  id: ID!
  rating: Float!
  comment: String
}

Even though the reviews field contains all the information needed from the first Connector, you can't map just $.reviews. You must specify each field—id, rating, and comment—individually. This enables the query planner to know that the rating field must be fetched from elsewhere.

Additional resources

Feedback

Ask Community