Selection Mapping

How to map API endpoint responses to GraphQL schemas with Apollo Connectors


Preview
Apollo Connectors are currently in public preview. To get started, you need an Apollo account with a GraphOS Trial or Enterprise plan.

Overview

Use a generalized selection mapping syntax with the @connect directive to transform JSON response payloads to match fields in your GraphQL subgraph schemas. Use this selection syntax with the selection argument.

The selection mapping determines how the endpoint's JSON response corresponds to your GraphQL schema. To use a field in the response, it must be listed in the selection string. This includes nested types.

GraphQL
Example connector with selection
1type Query {
2  products: Products
3    @connect(
4      http: { GET: "https://api.example.com/products" }
5      selection: """
6      id                   # 1
7      variation {          # 2
8        size {             # 3
9          width            # 4
10          height           # 5
11        }
12        color              # 6
13      }
14      """
15    )
16}
17
18type Product {
19  id: ID! # 1
20  variation: Variation # 2
21}
22
23type Variation {
24  size: Dimension # 3
25  color: String # 6
26}
27
28type Dimension {
29  width: Int # 4
30  height: Int # 5
31}

Examples

Basic direct selection

A basic, flat GraphQL type with fields that map to REST endpoint fields of the same names.

JSON
JSON Response
1{
2  "id": "1",
3  "username": "alice",
4  "email": "alice@example.com"
5}
GraphQL
Example: basic selection
1type Query {
2  user(id: ID!): User
3    @connect(
4      http: { GET: "https://api.example.com/users/{$args.id}" }
5      # The REST endpoint returns "username" and "email" in its
6      # response, and they're mapped directly to fields of
7      # the same name in the GraphQL schema.
8      selection: "id username email"
9    )
10}
11
12type User {
13  id: ID!
14  username: String!
15  email: String!
16}

Renaming fields

Mapping a JSON response field to a schema field of a different name uses the same syntax as GraphQL aliases.

JSON
JSON Response
1{
2  "user_id": "1",
3  "login": "alice",
4  "email_address": "alice@example.com"
5}
GraphQL
Example: renaming fields
1type Query {
2  user(id: ID!): User
3    @connect(
4      http: { GET: "https://api.example.com/users/{$args.id}" }
5      selection: """
6      id: user_id
7      username: login
8      email: email_address
9      """
10    )
11}
12
13type User {
14  id: ID!
15  username: String!
16  email: String!
17}

Unwrapping fields

When the JSON response includes nesting that you don't need in your schema, you can "unwrap" fields using the . prefix.

JSON
JSON Response
1{
2  "result": {
3    "id": "1",
4    "name": {
5      "first": "Alice"
6    },
7    "profile": {
8      "username": "alice",
9      "email": "alice@example.com"
10    }
11  }
12}
GraphQL
Example: unwrapping
1type Query {
2  user(id: ID!): User
3    @connect(
4      http: { GET: "https://api.example.com/users/{$args.id}" }
5      selection: """
6      $.result {
7        id
8        name: name.first
9        $.profile {
10          username
11          email
12        }
13      }
14      """
15    )
16}
17
18type User {
19  id: ID!
20  name: String!
21  username: String!
22  email: String!
23}

A leading $. is required when unwrapping a single property. Without $., it is interpreted as selecting the field to make an object. With $., it is interpreted as selecting the value.

JSON
1{ "message": "hello" }
SelectionResult
message{ "message": "hello" }
$.message"hello"
msg: message{ "msg": "hello" }

When selecting a path of properties, such as name.first, the $. is allowed but not required:

JSON
1{ "name": { "first": "Alice" } }
SelectionResult
$.name.first"Alice"
name.first"Alice"
$.name { first }{ "first": "Alice" }
name { first }{ "name": { "first": "Alice" } }

The simple form also applies when using value transformations. These are equivalent:

SelectionResult
message->match(["hello", "hi"], ["goodbye", "ciao"]){ "message": "hi" }
$.message->match(["hello", "hi"], ["goodbye", "ciao"]){ "message": "hi" }

Wrapping fields

You can create nested fields from a flat structure using a variation on the alias syntax. This is especially useful for converting a simple foreign key into an entity reference.

If the foreign keys are in a list, you can use the $ symbol to refer to items in the list.

JSON
JSON Response
1{
2  "id": "1",
3  "company_id": "2",
4  "address_ids": ["3", "4"]
5}
GraphQL
Example: wrapping fields
1type Query {
2  user(id: ID!): User
3    @connect(
4      http: { GET: "https://api.example.com/users/{$args.id}" }
5      selection: """
6      id
7      company: { id: company_id }
8      addresses: $.address_ids { id: $ }
9      """
10    )
11}
12
13type User {
14  id: ID!
15  company: Company
16  addresses: [Address]
17}
18
19type Company {
20  id: ID!
21}
22
23type Address {
24  id: ID!
25}

Arrays

Mapping arrays happens automatically, so you must ensure that your schema uses list types appropriately.

JSON
JSON Response
1{
2  "results": [
3    {
4      "id": "1",
5      "paymentCards": [
6        { "id": "1", "card_type": "Visa" },
7        { "id": "2", "card_type": "Mastercard" }
8      ],
9      "notes": ["note1", "note2"]
10    }
11  ]
12}
GraphQL
Example: wrapping fields
1type Query {
2  users: [User]  # list 1
3    @connect(
4      http: { GET: "https://api.example.com/users" }
5      selection: """
6      $.results {              # list 1
7        id
8        paymentCards {         # list 2
9          id
10          type: card_type
11        }
12        notes                  # list 3
13      }
14      """
15    )
16}
17
18type User {
19  id: ID!
20  paymentCards: [PaymentCard!] # list 2
21  notes: [String!] # list 3
22}
23
24type PaymentCard {
25  id: ID!
26  type: String
27}

Complex nested selection

A complex, nested GraphQL type, User, maps its fields from a REST endpoint returning multiple nested objects.

JSON Response
JSON
1{
2  "names": {
3    "username": "alice",
4    "email": "alice@example.com"
5  },
6  "bio": {
7    "dob": "1999-01-01",
8    "gender": "Female"
9  },
10  "contact": {
11    "phone": "555-555-5555",
12    "addresses": [{ "id": "1" }, { "id": "2" }]
13  },
14  "payments": [
15    {
16      "id": "1",
17      "card_number": "1234 5678 9012 3456",
18      "card_type": "Visa",
19      "exp_date": "12/23",
20      "default": true
21    }
22  ],
23  "cart": [
24    {
25      "product": { "id": "1" },
26      "amt": 2
27    }
28  ]
29}
GraphQL
1type Query {
2  user(id: ID!): User
3    @connect(
4      http: { path: "https://api.example.com/users/{$args.id}" }
5      selection: """
6      id: $args.id
7      $.names {
8        username
9        email
10      }
11      $.bio {
12        dob: birthDate
13        gender
14      }
15      phoneNumber: contact.phone
16      paymentMethods: $.payments {
17         id
18         cardNumber: card_number
19         cardType: card_type
20         expirationDate: exp_date
21         isDefault: default
22      }
23      shippingAddresses: contact.addresses { id }
24      shoppingCart: $.cart {
25         product { id }
26         quantity: amt
27      }
28      """
29    )
30}
31
32type User {
33  id: ID!
34  username: String!
35  email: String!
36  birthDate: Date
37  gender: String
38  phoneNumber: String
39  paymentMethods: [PaymentMethod]
40  shippingAddresses: [ShippingAddress]
41  shoppingCart: [CartItem]
42}
43
44type PaymentMethod {
45  id: ID!
46  cardNumber: String
47  cardType: String
48  expirationDate: String
49  isDefault: Boolean
50}
51
52type ShippingAddress {
53  id: ID!
54}
55
56type CartItem {
57  product: Product
58  quantity: Int
59}

Requests

Using selection mapping to create request bodies

Selection mapping can also be used to create request bodies for POST, PUT, and PATCH requests.

You can "unwrap" fields from field arguments using the $args variable.

GraphQL
Creating request bodies
1type Mutation {
2  createUser(input: CreateUserInput!): User
3    @connect(
4      http: {
5        POST: "https://api.example.com/users"
6        body: """
7        $args.input {
8          username
9          email
10          password
11        }
12        """
13      }
14      selection: "id username email"
15    )
16}
17
18type CreateUserInput {
19  username: String!
20  email: String!
21  password: String!
22}
JSON
Request body
1{
2  "username": "alice",
3  "email": "alice@example.com",
4  "password": "password123"
5}
💡 tip
Long selection strings can be broken up into multiple lines with GraphQL multiline string syntax ("""):
GraphQL
Example: multiline selection
selection: """
names: {
  first: first_name
  middle: middle_name
  last: last_name
}
"""

Literal values

You can use literal values in the selection string with $(). This is useful for adding constant values to the request body.

GraphQL
Literal values
1body: """
2hello: $("world")
3theAnswer: $(42)
4isTrue: $(true)
5anObject: $({ key: "value" })
6aList: $([1, 2, 3])
7"""

To avoid using the $() wrapper repeatedly, you can also wrap the whole object with $():

GraphQL
Literal values, again
1body: """
2$({
3  hello: "world",
4  theAnswer: 42,
5  isTrue: true,
6  anObject: { key: "value" },
7  aList: [1, 2, 3],
8})
9"""

Note that commas are required between the properties of the object literal.

Inside the $()expression, you can use any JSON literal: numbers, strings, booleans, arrays, objects, and null. You can also use variables like $args, $this, and selections from the response.

GraphQL
Expressions in literals
1selection: """
2names: $([             # a list field like `names: [String]`
3  $args.input.name,    # a variable
4  results->first.name  # a selection
5])
6"""
Result
Given the following inputs:
JSON
Variables
1{
2  "$args": {
3    "input": {
4      "name": "Alice"
5    }
6  }
7}
JSON
Response
1{
2  "results": [
3    { "name": "Bob" }
4    { "name": "Charlie" }
5  ]
6}
The result will be:
JSON
Result
1{ "names": ["Alice", "Bob"] }
 note
Currently, there are limitations in using literal values in the selection argument of the @connect directive.
  • Literal values can't be mapped to a field that returns a nested object or list of nested objects.
  • Scalar literal values (strings, numbers, booleans) and lists of scalars are allowed.
  • Literal objects and lists and lists of objects are allowed only if they're mapped to a custom scalar field.
  • All literal values in the body argument are always allowed.
GraphQL
1type Mutation {
2  createPost(input: CreatePostInput!): PostPayload
3    @connect(
4      http: {
5        POST: "https://api.example.com/posts"
6        body: "$({ id: 1, title: "Hello, world!" })" # ✅
7      }
8      selection: """
9      success: $(true) # ✅
10      post: $({ id: 1, title: "Hello, world!" }) # ❌
11      metadata: $({ key: "value" }) # ✅
12      """
13    )
14
15type PostPayload {
16  success: Boolean
17  post: Post # ⚠️ Object type
18  metadata: JSON
19}
20
21scalar JSON

Form URL encoding

By adding a Content-Type header of exactly application/x-www-form-urlencoded, GraphOS Router will encode the request body as a form URL encoded string.

GraphQL
Form URL encoding
1type Mutation {
2  createPost(input: CreatePostInput!): Post
3    @connect(
4      http: { POST: "https://api.example.com/posts" }
5      headers: { "Content-Type": "application/x-www-form-urlencoded" }
6      selection: """
7      $args.input {
8        title
9        content
10      }
11      """
12    )
13}

The request body is first mapped to an object:

JSON
1{
2  "title": "Hello, world!",
3  "content": "This is a post."
4}

Then, it is encoded as a form URL encoded string:

plaintext
1title=Hello%2C+world%21&content=This+is+a+post.
💡 tip
You can define the content-type header on the @source directive and all connectors with request bodies will use that content type and encoding (unless overridden headers defined on a connector).
GraphQL
1extend schema
2  @source(
3    name: "v1"
4    http: {
5      baseURL: "https://api.example.com"
6      headers: [
7        { "content-type": "application/x-www-form-urlencoded" }
8      ]
9    }
10  )
Form URL encoding details
  • List values are indexed starting from 0 using the list[0]=value syntax.
  • Nested objects use the parent[child]=value syntax.
  • Spaces are encoded as +.
GraphQL
Example: form URL encoding
1type Mutation {
2  example(input: ExampleInput!): Example
3    @connect(
4      http: { POST: "/example" }
5      headers: { "content-type": "application/x-www-form-urlencoded" }
6      selection: """
7      $args.input {
8        name
9        tags
10        addresses {
11          street
12          city
13          state
14          zip
15        }
16      }
17      """
18    )
19}
20
21input ExampleInput {
22  name: String!
23  tags: [String!]
24  addresses: [AddressInput!]
25}
26
27input AddressInput {
28  street: String!
29  city: String!
30  state: String!
31  zip: String!
32}
plaintext
Result (new lines added for readability)
1name=Example
2&tags[0]=tag1
3&tags[1]=tag2
4&addresses[0][street]=123+Main+St
5&addresses[0][city]=Anytown
6&addresses[0][state]=CA
7&addresses[0][zip]=12345
8&addresses[1][street]=456+Elm+St
9&addresses[1][city]=Othertown
10&addresses[1][state]=NY
11&addresses[1][zip]=54321

Variables

The selection string can use variables to refer to GraphQL arguments, sibling fields, or configuration values. These are the same variables available in URL templates.

ArgumentDescription
$argsThe GraphQL arguments passed to the field.
$thisThe parent object of the current GraphQL field. Used to refer to sibling GraphQL fields.
$configThe configuration passed to GraphOS Router.
$The value enclosed by the parent {...} selection, or the root value at the top level.
@The current value, which may differ from $ along a nested path.
$this and query planning
Using $this creates a dependency between the connector and fields on the parent object. This implies that another connector or another subgraph can provide this field.
GraphQL
1type Product {
2  id: ID!
3  reviews: [Review]
4    # Some other connector or subgraph provides the `id` value.
5    @connect(
6      http: { GET: "/products/{$this.id}/reviews" }
7      selection: "id rating comment"
8    )
9}
This is functionality equivalent to the @requires directive in Apollo Federation. This connector definition:
GraphQL
1type Product {
2  id: ID!
3  weight: Int
4  shippingCost: Int
5    @connect(http: { GET: "/shipping?weight=${this.weight}" }, selection: "$")
6}
is equivalent to:
GraphQL
1type Product @key(fields: "id") {
2  id: ID!
3  weight: Int @external
4  shippingCost: Int @requires(fields: "weight")
5}
In fact, you can combine connectors with @requires to create computed fields using REST APIs.

Value transformations

You can transform values in the response mapping using the ->method syntax.

The arguments to a method are JSON literals (numbers, strings, booleans, arrays, objects, and null) or variables ($, $args, $this, or $config).

In addition, the variable @ refers to the value being transformed, which is usually the same as $, but may differ from $ along a nested path. For example, in the value->echo({ wrapped: @ }) example below, @ refers to the value of value, while $ refers to the object containing value. Additionally, the colors->map({ name: @ }) method binds @ to each element in the $.colors list, while $ remains unchanged.

The following methods are available:

MethodDescriptionExample
echoEvaluates and returns its first argumentwrappedValue: value->echo({ wrapped: @ })
mapMaps a list of values to a new list of valuescolors: colors->map({ name: @ })
matchReplaces a value with a new value if it match another valuestatus: status->match([1, "one"], [2, "two"], [@, "other"])
firstReturns the first value in a listfirstColor: colors->first
lastReturns the last value in a listlastColor: colors->last
sliceReturns a slice of a listfirstTwoColors: colors->slice(0, 2)
sizeReturns the length of a listcolorCount: colors->size
entriesReturns a list of key-value pairskeyValuePairs: colors->entries

Value transformation recipes

Enum value mapping
GraphQL
1selection: """
2status: status->match(
3  ["active", "ACTIVE"],
4  ["not active", "INACTIVE"],
5  [@, "UNKNOWN"] # fallback — the value always matches `@`
6)
7"""
If a match isn't found, the result will be omitted and the field will be null if nullable or result in a validation error if non-nullable.
Convert a map into a list of key value pairs
JSON
Input
1{
2  "colors": {
3    "red": "#ff0000",
4    "green": "#00ff00",
5    "blue": "#0000ff"
6  }
7}
GraphQL
1selection: """
2colors: colors->entries
3"""
JSON
Result
1{
2  "colors": [
3    { "key": "red", "value": "#ff0000" },
4    { "key": "green", "value": "#00ff00" },
5    { "key": "blue", "value": "#0000ff" }
6  ]
7}
To use different names for keys and values, select the fields with aliases:
GraphQL
1selection: """
2colors: colors->entries {
3  name: key
4  hex: value
5}
6"""
JSON
Result
1{
2  "colors": [
3    { "name": "red", "hex": "#ff0000" },
4    { "name": "green", "hex": "#00ff00" },
5    { "name": "blue", "hex": "#0000ff" }
6  ]
7}
List management
Use the first item in a list:
GraphQL
1selection: """
2results->first
3"""
Wrap a single item in a list by using a literal list and selecting the property:
GraphQL
1selection: """
2$([$.result])
3"""
Feedback

Forums