directive @rest
The @rest
directive specifies the REST endpoint for a Query
or Mutation
field. When the field is resolved, a request is made to the REST endpoint.
A field annotated with @rest
can populate a deep tree from a REST response. For example, if a REST response contained a JSON object matching Customer
and Address
GraphQL types such as:
type Customer {
id: ID!
name: String
address: Address
}
type Address {
street: String
city: String
zip: String
}
then a Query
field defined as:
extend type Query {
customer(id:ID):Customer @rest(endpoint:"$url" configuration:"customers")
}
will resolve fields in Customer
and Address
.
@rest
is optimized for JSON, and automatically maps the JSON to the return GraphQL type. You can customize the extraction process using resultroot
and setters
.
Expansion variables
Expansion variables refer to values that can be used within the endpoint
,
postbody
, and header
directive arguments. They follow the format $name
or $name;
.
An expansion variable is replaced by the:
named value in the configuration (referenced by
configuration
argument to@rest
)JWT private claims from the incoming request when enabled
named field argument
Automatic generation is provided for URL query strings and body content. This process uses the argument name as the key/parameter
name and the argument value as the value. The name can be overridden
using arguments
.
Automatic generation supports only scalar arguments or arrays of scalars. GraphQL input type arguments are not processed.
An argument is considered non-null if any value (configuration, jwt, or argument) exists. This means, for example, a configuration can be used to establish a (hidden) default.
Arguments
arguments: [StepZen_Argument!]
The arguments
argument allows Graphql arguments to be renamed for automatic URL query
string generation and automatic body generation.
Example: arguments:[{argument: "graphQLArg", name:"backingRestCall"}...]
and with a value of graphQLArg:1 and type Int, then it would be rendered in the
endpoint
URI as ?backingRestCall=1
or in the (JSON) body as {"backingRestCall":1}
.
Only complete GraphQL argument names are supported, expressions accessing input fields of GraphQL arguments are not supported.
cachepolicy: StepZen_CachePolicy
The cachepolicy
argument defines the caching policy for the REST request.
If applied to a Query
field, the default is { strategy: DEFAULT }
.
And:
when
method=GET
the default is{ strategy: ON }
when
method=POST
the default is{ strategy: OFF }
when
method=PATCH
the default is{ strategy: OFF }
and you should not override it.when
method=PUT
the default is{ strategy: OFF }
and you should not override it.when
method=DELETE
the default is{ strategy: OFF }
and you should not override it. If applied to a field ofMutation
the default is{ strategy: OFF }
.
cel: String
The cel
argument is a string which expresses the extraction logic in the Content Extraction Language (CEL).
If the cel
parameter is provided, resultroot
, setters
, and filter
will be ignored
configuration: String
The configuration
argument specifies the configuration entry by name from the configurationset
.
Configuration values provide a hidden database of values that can be
used in endpoint
, postbody
, and headers
.
Values for configuration keys name
and id
(optional) are made available through the admin API
and therefore it is recommended that these values do not contain secrets. A convention is to
use id
to represent the REST api for usage in asset management and dependency tracking.
contenttype: String
The contenttype
argument specifies a Content-Type
for the postbody
. If postbody
is
provided, a Content-Type
must be set via contenttype
or headers
.
If no postbody
is set, the contenttype
may trigger automatic body
generation, for PATCH
, POST
and PUT
, with all arguments except those
that are null or have already been used in the endpoint
path or in
a header.
application/x-www-form-urlencoded
generates form-encoded content with argument based key value pairsapplication/json
builds a JSON object with argument key value pairs. GraphQL types will be used.
arguments
provides a renaming mechanism.
ecmascript: String
The ecmascript
argument contains ECMAScript code. ECMA 5.1 Scripts are supported.
Limits:
JSON parse will fail on broken UTF-16 surrogate pairs
Date
uses int instead of float per ECMAScriptWeakRef
andFinalizationRegistry
may result in unexpected memory usage
The ecmascript
argument allows you to modify HTTP request and response bodies. You can implement one or both of the following function signatures to manipulate these bodies:
bodyPOST
: modifies the HTTP request body before the request is made to the REST endpointtransformREST
: modifies the HTTP response body before the response is converted into the annotated field's GraphQL type
Both functions can use the built-in get
function to retrieve the annotated field's arguments.
endpoint: String!
The endpoint
argument defines the rest URI to call.
Expansion variables can be used in the endpoint in several ways:
As a query parameter value (e.g. email=$email) with automatic url encoding
As path element(s) (e.g. "https://api.a.org/users/$method/$id")
As the complete user name or password for basic authentication (e.g. "https://$user:$password@example.com")
As hostname segments or as a complete URL
Port cannot be a variable
Automatic query string generation will append all non-null arguments
except those which have already been used in the endpoint
path or
in a header. If the endpoint
URI has a query string, then only
the non-null nullable arguments will be appended.
Query string parameters may be renamed by using arguments
below.
filter: String
When the JSON returns a list, the filter
argument allows you to select specific JSON rows using a predicate defined based on the
result field names.
forwardheaders: [String!]
The forwardheaders
argument defines the list of headers forwarded from the incoming
GraphQL HTTP request to the HTTP call made to endpoint. Nonexistent
headers will forward as an empty header. Headers rendered
via configuration entries take priority.
headers: [StepZen_RequestHeader!]
The headers
argument specifies a list of headers to include in the request.
All headers, including duplicates, will be added in order.
Headers rendered via configuration entries and forwardheaders
will appear first.
The first Content-Type
header will be accepted if contenttype
is not set.
method: StepZen_HTTPMethod
The method
argument can be DELETE
, GET
, PATCH
, POST
, PUT
. Default is GET
. If
postbody
is present, then the default changes to POST
. The selection
of method affects other parameters.
pagination: StepZen_RESTPagination
The pagination
argument defines how pagination is handled by
the REST API call. The annotated field's type must be a Connection type
following the GraphQL pagination specification.
postbody: String
The postbody
argument can be specified if the method
is DELETE
, PATCH
, POST
, or PUT
.
The content of postbody
is treated as a golang template that is
executed using the Getter before populating the body field of the request.
postbody
is ignored for GET
requests.
resultroot: String
The resultroot
argument defines the path in the returned JSON object where the
parsing should start.
setters: [StepZen_FieldSetter!]
The setters
argument defines how fields in the annotated field type should be set from the JSON result.
Sometimes the name or structure of the content returned by a REST API doesn't exactly match the GraphQL type that the REST request will populate. In such cases, you can use setters
to map the values returned by a REST API response to the appropriate fields within the returned GraphQL type. Only fields that require remapping need to be specified; otherwise, appropriate defaults will be used.
To illustrate this concept, let's look at the following example JSON response:
{
"id": 194541,
"title": "The Title",
"slug": "the-url-2kgk",
"published_at": "2019-10-24T13:52:17Z",
"user": {
"name": "Brian Rinaldi",
"username": "remotesynth"
}
}
If the corresponding Article
GraphQL type has a field named published
but not published_at
, the published
field will not be populated. To resolve this, you can use a setter as follows:
{ "field": "published", "path": "published_at" }
setters
are also useful for extracting values from nested objects returned by a REST API.
For example, if Article
has a field user: User
then the value of user.name
will map by default to the field Article.user.name
.
If the requirement was instead to flatten the structure to set Article.author
and Article.username
then
these instances of StepZen_FieldSetter
would be required.
{ field: "author", path: "user.name" }
{ field: "username", path: "user.username" }
By using setters
, you ensure that your GraphQL types accurately reflect the data returned by REST APIs, even when the API responses don't align perfectly with your GraphQL schema.
transforms: [StepZen_Transform!]
The transforms
argument is a sequence of transformations to apply to the fetched JSON payload before parsing and extracting
the values of interest. The transformations will be applied in the sequence specified.
The value of resultroot
is not taken into consideration while processing tranformations.
transforms: [{pathpattern: "...", editor: "..."},
{pathpattern: "...", editor: "..."},
...]
Locations
Type System: FIELD_DEFINITION
Examples
GET
type Query {
get(a: Int, b: String): Response @rest(endpoint: "https://httpbingo.org/get")
}
type Response {
method: String
origin: String
url: String
args: JSON
}
GraphQL field arguments are automatically added as URL query parameters.
POST
type Query {
post(a: Int, b: String): Response
@rest(endpoint: "https://httpbingo.org/post", method: POST)
}
type Response {
method: String
origin: String
url: String
json: JSON
}
GraphQL field arguments are automatically added as the POST body with content-type application/json
.
Renaming fields in the response
To rename a field in the response, such as changing $id
to id
, use , use the following transforms argument:
transforms: [{pathpattern: ["item"], editor: "rename:$id,id"}]
The JSON will be transformed from:
{
"item": {
"$id": 23
}
}
To:
{
"item": {
"id": 23
}
}
Convert object to array format in response
If the backend response looks like:
{
"data": {
"2333": {
"name": "John Doe",
"age": 23
},
"3333": {
"name": "Jane Smith",
"age": 21
}
}
}
The objectToArray
editor addresses this by converting the key-value pairs into an array format.
@rest (endpoint: "...", transforms: [
{pathpattern: ["data","<>*"], editor: "objectToArray"}
])
This converts the response into:
{
"data": [
{
"name": "2333",
"value": {
"age": 23,
"name": "John Doe"
}
},
{
"name": "3333",
"value": {
"age": 21,
"name": "Jane Smith"
}
}
]
}
Delete field from a response
To delete a field from a response, such as removing the name
field in one field while retaining it in another, use the drop editor. For example:
type Query {
anonymous: [Customer]
@rest(
endpoint: "https://introspection.apis.stepzen.com/customers",
transforms: [
{ pathpattern: ["[]", "name"], editor: "drop" }
]
)
known: [Customer]
@rest(
endpoint: "https://json2api-customers-zlwadjbovq-uc.a.run.app/customers"
)
}
Manipulate JSON with jq
To restructure or manipulate JSON responses, use the jq
editor with custom jq
commands. For instance, to move the city
field into a nested address
object, apply the jq
formula .[] | {name, address: {city: .city}}
as follows:
transforms: [{pathpattern:[],editor:"jq:.[]|{name,address:{city:.city}}"}]
This modifies the original JSON:
[
{
"city": "Miami",
"name": "John Doe"
}
]
To the following:
{
"data": {
"customers": [
{
"name": "John Doe",
"address": {
"city": "Miami"
}
}
]
}
}
Using ECMAScript to modify HTTP request and response
type Query {
scriptExample(message: String!): JSON
@rest(
endpoint: "https://httpbin.org/anything",
method: POST,
ecmascript: """
function bodyPOST(s) {
let body = JSON.parse(s);
body.ExtraMessage = get("message");
return JSON.stringify(body);
}
function transformREST(s) {
let response = JSON.parse(s);
response.CustomMessage = get("message");
return JSON.stringify(response);
}
"""
)
}
In this example, an HTTP POST request is sent to https://httpbin.org/anything. The request payload is modified by the bodyPOST
function, adding an "ExtraMessage
" field that contains the value of the message
argument.
The transformREST
function modifies the response, adding a "CustomMessage
" field with the same message argument.
Testing with JSON return type
When using ecmascript
for transformation, it's helpful to set the return type of the query to JSON
initially. This makes it easier to test and debug before switching to a specific schema type.
type Query {
scriptExample(message: String!): JSON
@rest(
endpoint: "https://httpbin.org/anything",
method: POST,
ecmascript: """
function transformREST(s) {
let response = JSON.parse(s);
response.CustomMessage = get("message");
return JSON.stringify(response);
}
"""
)
}
Simulating a REST response
You can use ecmascript
to simulate a REST response by utilizing the stepzen:empty
endpoint. This is useful for testing GraphQL fields without an actual REST call. Here's an example:
type Query {
customers: JSON
@rest(
endpoint: "stepzen:empty",
ecmascript: """
function transformREST(s) {
return JSON.stringify({
"records": [
{ "name": "John Doe", "countryRegion": "US" },
{ "name": "Jane Smith", "countryRegion": "UK" }
]
});
}
"""
)
}
Mapping nested data with resultroot
Let’s explore an example using the Contentful Delivery API response structure.
{
"fields": {
"title": {
"en-US": "Hello, World!"
},
"body": {
"en-US": "Bacon is healthy!"
}
},
"metadata": {
// ...
},
"sys": {
// ...
}
}
In this example, the fields
object contains all the data required to populate a GraphQL type representing this content. Therefore, we set resultroot
to fields
to specify that as the path for the desired data:
contentfulPost(id: ID!): Post
@rest(
endpoint: "https://cdn.contentful.com/spaces/$spaceid/entries/$id"
resultroot: "fields"
configuration: "contentful_config"
)
Note: Setting resultroot
to fields
means the data in other parts of the response, such as metadata
and sys
, will not be accessible.
Handling arrays in response
In some cases, the data you need is nested within an array of items in the API response. For example, the Contentful API might return multiple entries as follows:
{
"sys": { "type": "Array" },
"skip": 0,
"limit": 100,
"total": 1256,
"items": [
{
"fields": {
// fields for this entry
}
}
]
}
Here, the data resides inside the fields
object within each item of the items
array. To map this, you need to set resultroot
to "items[].fields"
to indicate that the data should be extracted from the fields
object within the array items.
contentfulBlogs: [Blog]
@rest(
endpoint: "https://cdn.contentful.com/spaces/$spaceid/entries"
resultroot: "items[].fields"
configuration: "contentful_config"
)
This ensures that the schema can correctly pull the relevant data from the array for each blog post entry.