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
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:
{
"id": 1,
"name": "Lunar Rover Wheels"
}
You can use the following selection to map the id
and name
fields:
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 ("""
):
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
{
"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"
}
]
}
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:
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.
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
Read the other pages in this section to see examples for mapping fields, and working with arrays, enums, and literal values.
Refer to the mapping language reference for a complete overview of mapping syntax and usage.