Mapping Language

Convert data to and from GraphQL types using the Apollo Connectors mapping language


Preview
Apollo Connectors are currently in public preview. You need an Apollo account with a GraphOS Trial or Enterprise plan to get started. Update to router v.2.0.0-preview.4 and federation v.2.10.0-preview.4 to access the latest features.

This reference describes the Apollo Connectors mapping language. You use the mapping language anywhere data needs to be transformed from one form to another. This includes building URIs and headers, constructing request bodies when making requests, and mapping response data to GraphQL schema.

The basics

The mapping language uses a declarative approach to transforming data from one shape to another. Unlike traditional programming languages, it doesn't have state or loops. Since the mapping language borrows heavily from the GraphQL query language, some concepts like the alias syntax and array handling might look familiar.

Fields and paths

The most basic mapping expression selects a value from a path. For example, a URI path might include the expression $args.id. This expression means, "Use the id field of the $args variable." For example:

title="example
type Query {
  post(id: ID!): Post
    @connect(
      source: "jsonPlaceholder"
      http: { GET: "/posts/{$args.id}" }
      selection: "id title"
    )
}

When applied to an array, the result of a path is also an array. For example, if $args.filters is an array of objects, $args.filters.value is an array of the value property of each object.

JSON
$args
{
  "filters": [
    { "value": 1 },
    { "value": 2 },
    { "value": 3 }
  ]
}
JSON
Result of $args.filters.value
[1, 2, 3]

Creating objects

For this section, assume that $args contains this JSON object:

JSON
$args
{ "handle": "alice", "email": "alice@example.com" }

When you give a value a name, you create an object containing that value:

GraphQL
Expression
username: $args.handle
JSON
Result
{ "username": "alice" }

You can combine multiple fields to create an object with multiple properties:

GraphQL
Expression
username: $args.handle
email: $args.email
JSON
Result
{
  "username": "alice",
  "email": "alice@example.com"
}

Nested objects

You can create a new object as the property of another object using curly braces ({}):

GraphQL
Expression
user: {
  username: $args.handle
  email: $args.email
}
JSON
Result
{
  "user": {
    "username": "alice",
    "email": "alice@example.com"
  }
}

Subselections

If multiple properties from an object come from another object, you can use a subselection to avoid repeating the object name. The $ variable in the curly braces refers to the selected object—whatever comes directly before the {. In the following example, that's $args.

GraphQL
Expression
$args {
  username: $.handle
  email: $.email
}
JSON
Result
{
  "username": "alice",
  "email": "alice@example.com"
}

As shorthand, you can omit $. and refer to the fields only by their names:

GraphQL
Expression
$args {
  username: $.handle
  email: $.email
}
GraphQL
Equivalent expression
$args {
  username: handle
  email: email
}

When the name of the new field matches the name of the source, you can also omit the left-hand side:

GraphQL
Expression
$args {
  username: handle
  email: email
}
GraphQL
Equivalent expression
$args {
  username: handle
  email
}

You can combine this with the ability to create nested objects by adding back in the user: field name:

GraphQL
Expression
user: $args {
  username: handle
  email
}
JSON
Result
{
  "user": {
    "username": "alice",
    "email": "alice@example.com"
  }
}

Multiple levels

For more complex inputs, you can use multiple subselections to achieve the desired result without repetition. Suppose $args looks like this:

JSON
$args
{
  "user": {
    "handle": "alice",
    "email": "alice@example.com"
  },
  "company": {
    "name": "Acme",
    "address": "123 Main St"
  }
}

You can select all the data like this:

GraphQL
Expression
$args {
  user {
    username: handle
    email
  }
  organization: company {
    name
    address
  }
}
JSON
Result
{
  "user": {
    "username": "alice",
    "email": "alice@example.com"
  },
  "organization": {
    "name": "Acme",
    "address": "123 Main St"
  }
}
note
Because the user property being creating comes from the parent's property of the same name, you can use the shorthand syntax when creating the object.

Special case for selection

For the @connect directive's selection argument only, a top-level $ refers to the root of the response body from the source API. This means you can select basic GraphQL fields succinctly.

For example, given this JSON response:

JSON
JSON Response
{
  "id": "1",
  "username": "alice",
  "email": "alice@example.com"
}

You can use this basic selection to select all of a user's fields.

GraphQL
Example selection
type Query {
  user(id: ID!): User
    @connect(
      http: { GET: "https://api.example.com/users/{$args.id}" }
      selection: "id username email"
    )
}

type User {
  id: ID!
  username: String!
  email: String!
}

Since the REST endpoint returns username and email in its response, you can map them directly to fields of the same name in the GraphQL schema using the shorthand that omits the $.

Variables

Variables are the sources of data to be transformed. Variables can refer to:

  • GraphQL arguments

  • sibling fields

  • request and response data

  • information from the router

The available variables depend on the context of the expression.

Variable Definition Availability Notes

$args

The arguments passed to the field in the GraphQL query. For a field defined like product(id: ID!): Product, $args.id refers to the id argument passed to the product field.
  • Available in any expression in a @connect directive if arguments are defined for the field.
  • Not available in @source.

$this

The parent object of the current field. Can be used to access sibling fields. Learn about dependencies $this can create.
  • Only available on non-root types, that is, not within Query or Mutation connectors.
  • Not available in @source.

$config

The GraphOS Router configuration. Always available.

$context

Context set by router customizations like coprocessors.Only available if router customizations exist where context has been set.

$status

The HTTP status code of a response.
  • Only available in the selection field of @connect.
  • Not available in @source.

$

At the top level, $ refers to the root of the API response body.
Within a {...} sub-selection, $ refers to the value of the parent. See an example.
  • Only available on non-root types, that is, not within Query or Mutation connectors.
  • Not available in @source.

@

The value being transformed with a method. Behaves differently depending on the context. Learn more. Depends on the specific transformation method or mapping being applied.

Additional variable notes

$this

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
type Product {
  id: ID!
  reviews: [Review]
    # Some other connector or subgraph provides the `id` value.
    @connect(
      http: { GET: "/products/{$this.id}/reviews" }
      selection: "id rating comment"
    )
}

This feature of $this is equivalent to the @requires directive in Apollo Federation.

For example, the following connectors usage is equivalent to the following @requires usage in a subgraph schema.

GraphQL
type Product {
  id: ID!
  weight: Int
  shippingCost: Int
    @connect(
      http: {GET: "/shipping?weight=${this.weight}" }
      selection: "$"
    )
}
GraphQL
type Product @key(fields: "id") {
  id: ID!
  weight: Int @external
  shippingCost: Int @requires(fields: "weight")
}

In fact, you can combine connectors with @requires to create computed fields using REST APIs.

$

When used at the top-level of an expression, $ is only valid within the selection field of a connector, and refers to the root of the response body from the API. When used within a {...} sub-selection, $ refers to the value of the parent.

connectors
$.results {  # Here `$` refers to the response body
  id: $.id  # `$` refers to the `results` field
}

@

Refers to the value being transformed with a method, which is sometimes the same as $, but not always.

For example, in the value->echo({ wrapped: @ }) example in the methods section, @ refers to the value of value, while $ refers to the object containing value.

Additionally, in the colors->map({ name: @ }) example, the method binds @ to each element in the $.colors list, while $ remains unchanged.

Literal values

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

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

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

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

Inside the $()expression, you can use any JSON literal: numbers, strings, booleans, arrays, objects, and null.

note
Commas are required between the properties of the object literal.

Variables in literals

You can use variables, like $args and $this, in literal values.

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"""

Given the following inputs for the example above:

JSON
Variables
{
  "$args": {
    "input": {
      "name": "Alice"
    }
  }
}
JSON
Response
{
  "results": [
    { "name": "Bob" }
    { "name": "Charlie" }
  ]
}

The resulting selection is:

JSON
Result
{ "names": ["Alice", "Bob"] }

Using literals in with @connect

Currently, the following rules apply when using literal values with the @connect directive:

  1. All literal values in the body argument are always allowed.

  2. Scalar literal values (strings, numbers, booleans) and lists of scalars are allowed in the selection argument.

  3. Literal values can't be mapped to a field that returns a nested object or list of nested objects.

  4. Literal objects and lists and lists of objects are allowed only if they're mapped to a custom scalar field.

See corresponding examples of these rules below. The number in parentheses, for example, (1) on line 6 shows an example of rule 1.

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!" })" # ✅ (1)
7      }
8      selection: """
9      success: $(true) # ✅ (2)
10      post: $({ id: 1, title: "Hello, world!" }) # ❌ (3)
11      metadata: $({ key: "value" }) # ✅ (4)
12      """
13    )
14
15type PostPayload {
16  success: Boolean
17  post: Post # ⚠️ Object type
18  metadata: JSON
19}
20
21scalar JSON

Methods

You can transform values using the ->method syntax.

The arguments passed to a method can be either JSON literals (numbers, strings, booleans, arrays, objects, and null) or the following variables: $, $args, $this, or $config.

The mapping language provides the following methods:

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
jsonStringifyConverts a value to a JSON stringjsonBody: body->jsonStringify

Value transformation recipes

Enum value mapping
connectors
status: status->match(
  ["active", "ACTIVE"],
  ["not active", "INACTIVE"],
  [@, "UNKNOWN"] # fallback — the value always matches `@`
)
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
{
  "colors": {
    "red": "#ff0000",
    "green": "#00ff00",
    "blue": "#0000ff"
  }
}
connectors
colors: colors->entries
JSON
Result
{
  "colors": [
    { "key": "red", "value": "#ff0000" },
    { "key": "green", "value": "#00ff00" },
    { "key": "blue", "value": "#0000ff" }
  ]
}
To use different names for keys and values, select the fields with aliases:
connectors
colors: colors->entries {
  name: key
  hex: value
}
JSON
Result
{
  "colors": [
    { "name": "red", "hex": "#ff0000" },
    { "name": "green", "hex": "#00ff00" },
    { "name": "blue", "hex": "#0000ff" }
  ]
}
List management
Use the first item in a list:
connectors
results->first
Wrap a single item in a list by using a literal list and selecting the property:
connectors
$([$.result])
Feedback

Forums