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.
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 areDELETE
,GET
,PATCH
,POST
, andPUT
.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 theconfiguration
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 methodPOST
: allows setting a request body using thepostbody
attribute.PUT
: allows setting a request body using thepostbody
attribute.PATCH
: allows setting a request body using thepostbody
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 themethod
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 themessage
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 asjson
, 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 thepostbody
. In theendpoint
argument, we use$
to indicate where to insert a variable. In thepostbody
argument, we use theGoLang
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 thename
andmessage
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 ofCustomer
, which is the GraphQL object type of the query fieldcustomerById
. However, if instead ofname
, thetype Customer
had the fieldfullName
, we would need to make use of thesetters
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 thesetters
argument and running the query again. Without this argument, thefullName
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 anAddress
field that contains acountryRegion
field. We can pull-up thecountryRegion
field from the JSON response to thecountryRegion
field of the GraphQL response objectCustomer
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 toorders[]
instructs API Connect for GraphQL to resolve thecustomerOrders
field to a list ofOrder
objects from the JSON response. The[]
at the end oforders
informs API Connect for GraphQL to expect a list of objects. - Using
transforms
- Now that you are warmed up with
setters
andresultroot
, take a deeper look into more advanced capabilities that can be used to shape responses to the desired GraphQL type. Thetransforms
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 correspondingeditor
. 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 excellentjq
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 thetransforms
argument, so we expect that the HTTP response from theendpoint
request will be a JSON type. Also in this example, thepathpattern
is empty, indicating that the editor will be applied to the entire JSON response. The jq editor transforms this JSON by implicitly mapping theid
,email
, andname
fields from the JSON to the like-named fields of the GraphQLCustomer
type. It extracts values forCustomer
'scity
andcountryRegion
fields from the JSON'saddress.city
andaddress.countryRegion
fields. Finally, it calculates the value for theCustomer
'stotalshippingcost
field by adding togethershippingcosts
of all theorders
in the JSON. In total, thisjq
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.