Connecting a REST Service

When you connect your GraphQL API to a REST service, you can send HTTP requests and data to that service's endpoints, as well as receive and transform responses.

Tip: To quickly generate your GraphQL on a REST backend with the stepzen import mysql command, see Tutorial: Transform a REST API to GraphQL.

To connect to a REST service as your backend data source, start with an application view of the API, and then use the @rest directive to connect it to the backend REST service. This method allows you to have a curated application experience, and is explained in the following sections.

About the @rest directive

Any Query or Mutation field in your GraphQL schema can be annotated with the @rest directive. Briefly, the form is:

type EchoResponse {
  url: String
}
type Query {
  echo (number: Int, message: String): EchoResponse
  @rest (endpoint: "https://httpbin.org/anything")
}

This schema attempts to resolve the return type EchoResponse by making an HTTP call to the address given in the argument endpoint of the directive @rest when the field echo is selected in a query. There are several other arguments that allow you to make the correct HTTP request and shape the response, explained in the following sections.

Building the HTTP request

The only required argument to the @rest directive is endpoint. The endpoint argument specifies the URL for the HTTP request. By default, any field arguments (for example, number and message in the preceding query) are automatically appended to the endpoint as the query string. The optional arguments for the @rest directive which can be used to build the request are as follows:

  • method: the HTTP method to use for the request.

    The default value is GET. Supported values are DELETE, GET, PATCH, POST, and PUT.

  • headers: the HTTP headers to send with the request.

    Variables are supported. See the Custom directives reference to learn more.

  • contenttype: the Content-Type header to send with the request (shortcut argument).

  • postbody: the HTTP message body for the request.

    Variables are supported. See the Custom directives reference to learn more.

  • arguments: specifies to rename the field arguments used in the request query string.

    See the Custom directives reference to learn more.

  • configuration: specifies a configuration entry from which variables, commonly secrets such as API keys, can be used in the request.

    See the Custom directives reference to learn more.

  • ecmascript: a preprocessing script which modifies the message body for the request.

    See the Custom directives reference to learn more.

This combination of arguments allows you to customize every aspect of the HTTP request that API Connect for GraphQL uses to satisfy operation requirements.

Let's look at a more complete example of using the @rest directive to configure an HTTP request.

Consider the following schema, which defines a single field that when selected in a query sends a request to the https://httpbin.org/anything endpoint:

type Query {
  anything(message: String): JSON
  @rest (endpoint: "https://httpbin.org/anything")
}

This is a fully-functional schema. The anything field on the Query type returns the JSON scalar type, which makes it easy for us to focus on showing the effect of arguments used to build the HTTP Request.

Setting HTTP headers
Let's add two headers to the request. The first header is the User-Agent header, and the second is the X-Api-Key header.
type Query {
  anything(message: String): JSON
  @rest (
    endpoint: "https://httpbin.org/anything"
    headers: [
      {name: "User-Agent", value: "Essentials"}
      {name: "X-Api-Key", value: "12345"}
    ]
    )
}

If you deploy this schema and request the anything field from a query operation, you will see your headers reflected in the response.

Tip: Instead of hard-coding your API key in the schema, you can use the configuration argument to specify a configuration from which the API key can be retrieved. See the Custom directives reference to learn more.
Setting the HTTP method
Use the method attribute of the @rest directive to specify the HTTP method used to call a REST API. The supported methods are:
  • GET : the default method
  • POST: allows setting a request body using the postbody attribute.
  • PUT: allows setting a request body using the postbody attribute.
  • PATCH: allows setting a request body using the postbody attribute.
  • DELETE

Now let's change this to a POST request, and see how the message argument is used in the request body. Edit the schema to add the method argument:

type Query {
  anything(message: String): JSON
  @rest (
    endpoint: "https://httpbin.org/anything"
    method: POST
    headers: [
      {name: "User-Agent", value: "Essentials"}
      {name: "X-Api-Key", value: "12345"}
    ]
    )
}

If you deploy this schema and request the anything field from a query operation, supplying "Hello World" for the message argument, you will see that AAPI Connect for GraphQL sent a POST request with the request body as json, like this:

{"message":"Hello World"}
Setting the HTTP request body
Instead of the default request body, let's specify a more complex template. Update the schema as follows, setting the postbody argument as shown:
type Query {
  anything(message: String): JSON
  @rest (
    endpoint: "https://httpbin.org/anything"
    method: POST
    headers: [
      {name: "User-Agent", value: "Essentials"}
      {name: "X-Api-Key", value: "12345"}
    ]
    postbody: """
      {
        "user": {
          "id": "1000",
          "name": "The User"
         }
      }
    """
    )
}

If you deploy this schema and request the anything field from a query operation, you will see that API Connect for GraphQL has sent a POST request with the request body as json, like this:

{
  "user": {
    "id": "1000",
    "name": "The User"
  }
}

For more information on the postbody argument, see the Custom directives reference.

Using variables
Let's look at an example that uses variables in the endpoint and the postbody. In the endpoint argument, we use $ to indicate where to insert a variable. In the postbody argument, we use the GoLang template format to indicate where to insert variables. Update the schema as follows:
type Query {
  anything(id: ID, name: String, message: String): JSON
  @rest (
    endpoint: "https://httpbin.org/anything/users/$id"
    method: POST
    headers: [
      {name: "User-Agent", value: "Essentials"}
      {name: "X-Api-Key", value: "12345"}
    ]
    postbody: """
      {
        "user": {
          "name": "{{.Get "name"}}",
          "message": "{{.Get "message"}}"
         }
      }
    """
    )
}

Now deploy this schema and send a few queries to see how API Connect for GraphQL converts the arguments into portions of the HTTP request. Notice that the id argument is substituted into a path element in the HTTP request URL and the name and message arguments are substituted into the HTTP request body.

We started with a simple HTTP GET request and converted it to a POST request with a custom path and request body. If necessary, even more complex requests can be created. For more information on the @rest directive arguments, see the Custom directives reference.

Shaping the HTTP response

Often the data that comes back from a REST backend is not identical to the result type of the GraphQL operation. The @rest directive supports the following additional arguments to help you shape the REST response to the requested GraphQL type.

  • resultroot: a path to extract from the JSON response to map to the operation type.

  • setters: mapping from JSON response values to the fields of the GraphQL result.

    Custom directives - transforms: editors for simplifying common and more complex transformations. See the Custom directives reference to learn more.

  • ecmascript: JavaScript for transforming a REST response to JSON or XML.

    See the Custom directives reference to learn more.

These arguments allow you to convert any response from the HTTP request to match the GraphQL type of the operation.

Let's create a new Query type field using @rest and transform the HTTP response to match the field's type. We'll be using a sample REST API available at https://introspection.apis.stepzen.com/customers.

Start with the following schema:

type Customer {
  email: String
  id: ID
  name: String
}
type Query {
  customerById(id: ID!): Customer
  @rest(
    endpoint: "https://introspection.apis.stepzen.com/customers/$id"
  )
}
Using setters
The schema in the preceding section works correctly, since the JSON response from the REST API specified by endpoint has fields that match the fields of Customer, which is the GraphQL object type of the query field customerById. However, if instead of name, the type Customer had the field fullName, we would need to make use of the setters argument for the @rest directive as follows:
type Customer {
  email: String
  id: ID
  fullName: String
}
type Query {
  customerById(id: ID!): Customer
  @rest(
    endpoint: "https://introspection.apis.stepzen.com/customers/$id"
    setters: [{field: "fullName", path: "name"}]
  )
}

You can see the effect of the setters by deploying this schema to API Connect for GraphQL and running a query, then commenting out the setters argument and running the query again. Without this argument, the fullName field will be null.

The setters argument can also be used to "pull-up" values from inside a nested JSON object to the fields in the GraphQL object. For example, the JSON response of the customers API includes an Address field that contains a countryRegion field. We can pull-up the countryRegion field from the JSON response to the countryRegion field of the GraphQL response object Customer as follows:

type Customer {
  email: String
  id: ID
  fullName: String
  countryRegion: String
}
type Query {
  customerById(id: ID!): Customer
  @rest(
    endpoint: "https://introspection.apis.stepzen.com/customers/$id"
    setters: [
      {field: "fullName", path: "name"}, 
      {field: "countryRegion", path: "address.countryRegion"}
    ]
  )
}
Using resultroot
REST APIs will often return more data than you wish to expose to your users. For example, our sample customers API returns customer order information along with customer address information. If we want to query only the order information for a customer, we can use the resultroot argument to specify how to exract only this information from the JSON response. Consider the following schema:
type Order {
  carrier: String
  customerId: ID
  id: ID
  trackingId: ID
}

type Query {
  customerOrders(customerId: ID!): [Order]
  @rest(
    endpoint: "https://introspection.apis.stepzen.com/customers/$customerId"
    resultroot: "orders[]"
  )
}

Here, setting the resultroot argument to orders[] instructs API Connect for GraphQL to resolve the customerOrders field to a list of Order objects from the JSON response. The [] at the end of orders informs API Connect for GraphQL to expect a list of objects.

Using transforms
Now that you are warmed up with setters and resultroot, take a deeper look into more advanced capabilities that can be used to shape responses to the desired GraphQL type. The transforms argument of @rest allows for some very common and sophisticated transformations of the responses. It takes the following form:
transforms: [{pathpattern: "...", editor: "..."},
             {pathpattern: "...", editor: "..."},
             ...]

The list of objects of the form {pathpattern: "...", editor: "..."} must contain at least one object. If there is more than one object, the objects are processed in the order in which they appear, the output of one providing the input for the next.

Each pathpattern is a list of strings each specifying a part of the path. The resulting path pattern indicates which parts of the JSON value will be processed by the corresponding editor. For more information, see the Custom directives reference.

Transform using jq
One of the available transform editors is jq. This editor supports transforming responses using formulas from the excellent jq tool, and takes the form:
transforms[{pathpattern: ["...","..."], editor: "jq:jq-expression"}]

Let's apply a jq editor transform to a field in the Query type of our customer schema. Using the same customers API as before, we will flatten the records returned from the API, and instead of returning details on each order, we will calculate the total shipping cost for the customer.

The schema for this example looks like:

type Customer {
  id: ID
  email: String
  name: String
  city: String
  countryRegion: String
  totalShippingCost: Float
}

type Query {
  customerShippingCost(id: ID!): Customer
  @rest (
    endpoint: "https://introspection.apis.stepzen.com/customers/$id"
    transforms: [
      {
        pathpattern:[],
        editor:"jq:map({id,email,name,city:.address.city,countryRegion:.address.countryRegion,totalShippingCost:([.orders[].shippingCost]|add)})"
        }
    ]
  )
}

In this example, the jq editor is the only object of the transforms argument, so we expect that the HTTP response from the endpoint request will be a JSON type. Also in this example, the pathpattern is empty, indicating that the editor will be applied to the entire JSON response. The jq editor transforms this JSON by implicitly mapping the id, email, and name fields from the JSON to the like-named fields of the GraphQL Customer type. It extracts values for Customer's city and countryRegion fields from the JSON's address.city and address.countryRegion fields. Finally, it calculates the value for the Customer's totalshippingcost field by adding together shippingcosts of all the orders in the JSON. In total, this jq formula converts a JSON object from this:

[
  {
    "address": {
      "city": "Edinburgh",
      "countryRegion": "UK",
      "id": 4,
      "postalCode": "EH1 1DR",
      "stateProvince": "Midlothian",
      "street": "777 Highlands Dr"
    },
    "email": "jane.xiu@example.com",
    "id": 4,
    "name": "Jane Xiu",
    "orders": [
      {
        "carrier": "ups",
        "createdAt": "2020-08-05",
        "customerId": 4,
        "id": 1,
        "shippingCost": 3,
        "trackingId": "1Z881F5V0397269080"
      },
      {
        "carrier": "ups",
        "createdAt": "2020-08-02",
        "customerId": 4,
        "id": 7,
        "shippingCost": 1,
        "trackingId": "1Z881F5V0397269080"
      }
    ]
  }
]

to this:

[
  {
    "id": 4,
    "email": "jane.xiu@example.com",
    "name": "Jane Xiu",
    "city": "Edinburgh",
    "countryRegion": "UK",
    "totalShippingCost": 4
  }
]

Learn more about @rest

In this section, we've covered the basics of the @rest directive. We've covered ways to configure the HTTP request API Connect for GraphQL sends to the API, and we've also looked at how to transform the responses. What we reviewed in this document is not an exhaustive list of the capabilities of the @rest directive.

For more information on how to format the HTTP request body, review Sending a POST request to a REST service. For information on mapping REST API paging to GraphQL paging, review Paginating a REST API as a data source.

See the Custom directives reference for a full list of the capabilities of the @rest directive.