StepZen is now part of IBM. For the most recent product information and updates go to
https://www.ibm.com/products/stepzen

StepZen Directives Reference

GraphQL directives are declarative constructs that simplify how you build GraphQL APIs, and assemble graphs of graphs.

The schema that defines your GraphQL APIs incorporates directives that allow you to assemble GraphQL declaratively in StepZen and control how your schemas are executed. This section describes StepZen directives and their application when building and running GraphQL APIs.

Next to the built-in directives, StepZen also provides a set of custom directives that allow you to connect to external data sources, such as REST APIs, databases, and other GraphQL APIs. Or to alter the data returned from an operation.

In GraphQL there are two types of directives: type system directives and executable directives. Type system directives are used to annotate types and fields in a GraphQL schema using SDL. Executable directives are used to control the execution of a GraphQL operation in a GraphQL document when requesting or mutating data.

Type System Directives

Type system directives are used to annotate types and fields in a GraphQL schema using SDL. The following type system directives are supported by StepZen:

@rest

@rest (endpoint: String!, configuration: String, resultroot: String, setters: [{field: String, path: String}], headers: [{name: String, value: String}])

@rest enables you to connect any REST API as a data source for a GraphQL schema. The directive can be applied to a GraphQL query, so the query's result is populated with data returned from REST API, often mapped through a series of transformations to the query's type.

For REST backends, execution of the query results in an HTTP call (defaults to GET) built from the specified endpoint enhanced by values in the @rest arguments. The arguments for the @rest directive are:

For examples of how to use the @rest directive arguments, see Connect a REST Service Using @rest

endpoint

This value is required. The endpoint argument specifies the URL for the HTTP request. Endpoint is a string value that can contain variables preceded by a $, which are replaced by StepZen. These variables can match query arguments or variables in a corresponding configuration of the same name. For example, $username in the endpoint string will be replaced by the value of a username query argument.

By default, StepZen automatically appends all query arguments to the endpoint URL query string (except when using POST endpoints with a default postbody). All argument values are automatically URL-encoded so that when querying your StepZen GraphQL endpoint you can safely pass any argument "as is".

For example, in the following schema the actual endpoint URL is https://httpbin.org/anything?number=$number&message=$message.

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

If you want to disable this default and take full control over the endpoint URL, add a configuration to your @rest directive that sets the stepzen.queryextensionguard attribute to true (in config.yaml). For example, with in the following schema only the message argument is appended to the endpoint URL:

type Query {
  echo(number: Int, message: String): EchoResponse
    @rest(
      endpoint: "https://httpbin.org/anything?message=$message"
      configuration: "your_config_name"
    )
}
configurationset:
  - configuration:
      name: "your_config_name"
      stepzen.queryextensionguard: true

There is a special value for endpoint stepzen:empty that can be used in conjunction with the ecmascript argument. stepzen:empty configures the StepZen server to make no http request, but still process the ecmascript argument, allowing you to create responses entirely from ecmascript.

configuration

This value is optional. The connection details to pass down into headers (e.g. authorization) - specifying to StepZen which configuration to use for this endpoint. StepZen configurations are stored in a config.yaml file and are assigned names.

For example, a named configuration within config.yaml called github_config will be referenced by a configuration property of @rest as configuration: github_config. A configuration can contain things like API keys needed to connect to a REST API or other configuration values that may be needed to construct the endpoint URL.

method

This value is optional. The default value for method is GET, other values can be DELETE, PATCH, POST, or PUT. If postbody is set on @rest, then the default changes to POST. The selection of method affects the configuration properties you can use.

type Mutation {
  sendPostRequest(to: String!, from: String!, template_id: String!): Email
    @rest(method: POST, endpoint: "https://api.sendgrid.com/v3/mail/send")
}

resultroot

This value is optional. In cases where the data to populate the GraphQL type is not in the root object of the result from a REST API, use resultroot to specify the path that StepZen is to use as the root.

Let's look at an example. This is the structure of a response from the Contentful Delivery API:

{
  "fields": {
    "title": {
      "en-US": "Hello, World!"
    },
    "body": {
      "en-US": "Bacon is healthy!"
    }
  },
  "metadata": {
    // ...
  },
  "sys": {
    // ...
  }
}

In this example, fields contains all the data to populate a type representing this content object. Therefore, resultroot is set to fields as shown here:

contentfulPost(id: ID!): Post
    @rest(
      endpoint: "https://cdn.contentful.com/spaces/$spaceid/entries/$id"
      resultroot: "fields"
      configuration: "contentful_config"
    )

Important: The value of setters is mapped from the resultroot. In this example, this makes the data under metadata and sys inaccessible.

In some cases, the data to populate the GraphQL type is located inside of an array of items of the result from a REST API. Therefore, you must set the resultroot inside of an array of items:

{
  "sys": { "type": "Array" },
  "skip": 0,
  "limit": 100,
  "total": 1256,
  "items": [
    {
      "fields": {
        /* list of fields for this entry */
      }
    }
  ]
}

In above example, Contentful returns all the entries and we need to set the value of the root to fields inside the array of items.

You can do this by adding an empty array notation in the resultroot as in the following example:

contentfulBlogs: [Blog]
    @rest(
      endpoint: "https://cdn.contentful.com/spaces/$spaceid/entries"
      resultroot: "items[].fields"
      configuration: "contentful_config"
    )

setters

This value is optional. Sometimes the name or structure of the content returned by a REST API doesn't exactly match the GraphQL type that the query will populate. In these cases, you can use setters to map the values returned by a REST API result to the appropriate fields within the returned GraphQL type. (Only fields that need to be remapped need to be specified, otherwise StepZen makes good default assumptions.)

setters takes an array of objects each containing a field and path. The field is the property in the GraphQL type returned by the query that the value of field should be set to. The path is the path to the value in the endpoint's JSON result.

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 of published but not published_at, StepZen will not be able to automatically map the value returned by the REST API to the value in the GraphQL type.

To resolve this, add a setter with the following values:

{ field: "published", path: "published_at" }

Setters are also useful for mapping values in nested objects, returned by a REST API.

In the example above, the value of user.name cannot be automatically mapped to a field in the GraphQL type. You have two options:

  • Create another type for User that has the corresponding fields and then use the resultroot property to user.
  • Flatten the values and add them to the Article type using the following setters:
{ field: "author", path: "user.name" }
{ field: "username", path: "user.username" }

Here's an example of a @rest directive in a file called article.graphql:

type Article {
  id: ID!
  title: String!
  description: String
  cover_image: String
  username: String!
  github_username: String!
}
type Query {
  myArticles(username: String!): [Article]
    @rest(
      endpoint: "https://dev.to/api/articles?username=$username"
      configuration: "dev_config"
      setters: [
        { field: "username", path: "user.username" }
        { field: "github_username", path: "user.github_username" }
      ]
    )
  }
}

Let's pull out the @rest directive to take a closer look:

@rest(
  endpoint: "https://dev.to/api/articles?username=$username"
  configuration: "dev_config"
  setters: [
    { field: "username", path: "user.username" }
    { field: "github_username", path: "user.github_username" }
  ]
)

endpoint, configuration, and setters are all arguments to the @rest directive. That is, they give StepZen the information it needs to connect to the REST API:

  • endpoint sets the endpoint of the API.
  • configuration references the configuration file by its name value.
  • setters specifies the names of the fields that will be used and their values from their paths.

Character that are not numbers, letters, or underscores If the value for a path in setters contains any character that is not a number, letter, or underscore, you must wrap the value with back-ticks. For example:

The path value in: { field: "terminal", path: "terminal-name" }

must be wrapped in back ticks so the line reads as: { field: "terminal", path: "`terminal-name`" }

Note that the value accepts template literals, but the field will not, for example, { field: "`terminal`", path: "`terminal-name`" } will not work.

filter

This value is optional. The filter argument will filter results returned from the REST API based on a condition so that the query will only returned the filtered results. It accepts any boolean operator (e.g. ==, !=, >. <, >=, <=).

For example, the following filter returns only the record for the email that matches the string:

newsletterList: [Subscriber]
@rest(
    endpoint: "https://newapi.getpop.org/wp-json/newsletter/v1/subscriptions"
    filter: "email==\"quezarapadon@quebrulacha.net\""
)

Note: Special characters, like the quotes above, need to be escaped with \. Using variables within a filter condition are not supported at this time.

headers

This value is optional. The headers argument specifies headers as name and $variable. Variables are passed into header fields from the configuration or arguments in the query

@rest(
  headers: [
    { name: "Authorization" value: "Bearer $bearerToken" }
  ]

In this example, the $bearerToken variable can come from a configuration or a query argument.

forwardheaders

This value is optional. This 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.

@rest(
  forwardheaders: ["Authorization", "User-Agent"]

In this example, the HTTP call made to the endpoint will include the Authorization and User-Agent headers from the incoming GraphQL HTTP request.

postbody

This value is optional. You set postbody when you need customize the automatically generated body for a PATCH, POST, or PUT request. Let's look at some examples to help explain how this argument can be used.

StepZen automatically generates the request body using all query or mutation arguments. For example, consider a query defined as follows:


type Query {
  formPost(firstName: String! lastName: String!): JSON
    @rest(
      method: POST
      endpoint: "https://httpbin.org/post"
    )
}

This query accepts two string values and returns a JSON scalar. The contenttype has not been set, so defaults to application/json. The postbody has not been set, so StepZen automatically generates a simple JSON body using the query argument names and values:

{
  "firstName":"value",
  "lastName":"value"
}

If we change the contenttype to application/x-www-form-urlencoded, StepZen generates a form-encoded request body request body.

type Query {
  formPost(firstName: String! lastName: String!): JSON
    @rest(
      method: POST
      contenttype: "application/x-www-form-urlencoded"
      endpoint: "https://httpbin.org/post"
    )
}

The above schema code results in a body of:

firstName=value&lastName=value

If you need to change the field names in the automatically generated bodies, you can do that using the arguments attribute for the directive. In the example below we have changed the names from camelCase to snake_case:

type Query {
  formPost(firstName: String! lastName: String!): JSON
    @rest(
      method: POST
      endpoint: "https://httpbin.org/post"
      arguments: [
        {argument:"firstName", name:"first_name"},
        {argument:"lastName", name:"last_name"}
      ]
    )
}

This works for both application/json and x-www-form-urlencoded bodies.

We only need to use postbody when we need to customize the request body more than simply renaming fields. For example, if we need to add fields, or generate a more complex structure. When performing variable substitution in the postbody, we need to use the Go language template syntax.

For example, consider a query that uses the Client Credentials flow to obtain an OAuth token. Since the grant_type argument is not included in the query definition, we need to add it to the request body.

type Query {
  token(client_id: String! client_secret: String!): Token
    @rest(
      endpoint: "<oauth token endpoint"
      method: POST
      contenttype: "x-www-form-urlencoded"
      postbody: """
      grant_type=client_credentials&client_id={{ .Get "client_id" }}&client_secret={{ .Get "client_secret"}}
      """
    )
}

Consider a JSON payload from our earlier example, but our JSON payload needs to be more complex:

{"user":{
  "firstName": "first name"
  "lastName": "last name"
  }
}
type Query {
  logUser(firstName: String! lastName: String!): JSON
    @rest(
      endpoint: "https://httbin.org/post"
      postbody: """
      {
        "user": {
          "firstName": "{{ .Get "firstName" }}",
          "lastName": "{{ .Get "lastName" }}"
        }
      }
      """
    )
}

As we are not automatically generating the body of the request, the GraphQL query arguments are automatically appended to the request URL as as parameters in a query string, e.g. https://httpbin.org/post?firstName=<value>&lastName=<value>. We can tell StepZen not to append GraphQL query arguments as query string parameters by setting the stepzen.queryextensionguard attribute to true in the config.yaml file as follows:

configurationset:
  - configuration:
      name: "config"
      stepzen.queryextensionguard: true

Our final query schema is:

type Query {
  logUser(firstName: String! lastName: String!): JSON
    @rest(
      endpoint: "https://httbin.org/post"
      postbody: """
      {
        "user": {
          "firstName": "{{ .Get "firstName" }}",
          "lastName": "{{ .Get "lastName" }}"
        }
      }
      """
      configuration: "config"
    )
}

PATCH with autopostbody works for the simplest form of patch using nullable arguments.

type Mutation {
  updateUser_Optionals(first_name: String last_name: String): JSON
    @rest(
      endpoint: "https://httpbin.org/patch"
      method: PATCH
    )
}

Query:

mutation stepzenPatch {
  updateUser_Optionals(first_name: "John", last_name: "Doe")
}

To pass JSON as an argument in the GraphQL Mutation, the argument JSONPatch must be added as a variable to the postbody argument:

type Mutation {
  updateUser_PatchString(JSONPatch: String): JSON
    @rest(
      endpoint: "https://httpbin.org/patch"
      method: PATCH
      postbody: """{{ .Get "JSONPatch" }}"""
    )
}

Query:

mutation MyMutation {
  updateUser_PatchString(JSONPatch: "{\"name\":\"John\"}")
}

An example of when updateUser_PatchString would be used rather than updateUser_Optionals is when an application or operation, such as @sequence, passes JSON as an argument.

The JSONPatch can also be built using the JSON scalar type in which case, the following works:

type Mutation {
  updateUser_Patch(JSONPatch: JSON): JSON
    @rest(
      endpoint: "https://httpbin.org/patch"
      method: PATCH
      postbody: """{{ .GetJSON "JSONPatch" }}"""
    )

DELETE does automatic query string extension, passing all GraphQL arguments by default. postbody may be specified if needed; there is no postbody autogeneration.

type Mutation {
  deleteUser(userID: ID!): JSON
    @rest(
      endpoint: "https://httpbin.org/delete"
      method: DELETE
    )
}

The query would result in the following URL being called with DELETE https://httpbin.org/delete?userID=1

mutation MyMutation {
  deleteUser(userID: "1")
}

cachepolicy

This value is optional. The cachepolicy property overrides the default cache behavior.

In StepZen, caching is automatic. The system knows when the backend call is a REST call and sets up a cache with a default time-to-live (TTL) of one minute. Because StepZen uses multiple servers to handle traffic, these caches are available within the execution of a query and across queries.

cachepolicy can be set to the following values:

  • DEFAULT : Default cache strategy. The DEFAULT strategy varies according to field and method.

    If applied to a field of Query the default is { strategy: DEFAULT } and:

    • When method=GET the default is { strategy: ON }.
    • When method=PATCH the default is { strategy: OFF }
    • When method=POST the default is { strategy: OFF }.
    • When method=PUT the default is { strategy: OFF }
    • When method=DELETE the default is { strategy: OFF }

    If applied to a Mutation field, the default is { strategy: OFF }.

  • ON : Use cache.

  • OFF : Ignore cache.

  • FORCE : The request is forced and the result is placed in the cache.

@rest(
  endpoint: "yourendpoint.com",
  cachepolicy: { strategy: ON }
)

transforms

This value is optional. The transforms argument "transforms" the @rest JSON response. The transforms argument takes the following form:

transforms: [{pathpattern: "...", editor: "..."},
             {pathpattern: "...", editor: "..."},
             ...]

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

The pathpattern as the name implies specifies what objects are included in the transform processing by specifying the allowed paths. Here are the ways to select a JSON path:

  • term matches a specific object key, like pathpattern:["records"].
  • [num] matches a specific array index, like pathpattern:["records",[2]].
  • <> matches exactly one object key.
  • [] matches exactly one array index.
  • * matches either exactly one key, or a exactly one index.
  • <>* matches a sequence of zero or more keys, like pathpattern:["records", "<>*"] would match records, records.name, and including those nested like records[1].name, records.artist.name, etc.
  • []* matches a sequence of zero or more indices.
  • ** matches a sequence of zero or more anything, keys or indices. pathpattern:[] (an empty pathpattern) indicates that the editor should be applied at the root.

Available editors are listed below. All of the editors except xml2json expect JSON input. xml2json expects XML input. The transforms are applied in the order they appear in the transforms argument in the schema.

  • xml2json : Transform XML to JSON.

    SOAP backends commonly return XML. The transforms argument below will properly transcribe an XML response to JSON.

      transforms: [{pathpattern: [], editor: "xml2json"}]

    The following rules apply to the xml2json editor:

    • if an xml2json editor is present, it must be specified in the first or only input object.
    • if the REST response (or the output of any preceding ecmascript transform) produces an XML value, then there must be a transform argument, and the xml2json editor must be the first input object.
    • the only valid pathpattern that can accompany an xml2json editor is [] (root) since the input is not JSON
  • jq:<jq formula> : Apply a jq formula to the JSON.

    Tip: You can can also test out your jq formula against the entire tree using any of the jq online tools (such as jqplay) and then copy the jq formula directly into your StepZen schema, often without any changes. Make pathpattern:[] in this case. While this approach often works, sometimes jq treatment of arrays etc. differs subtly, so you might have to modify the formula that works outside StepZen a bit for it to work inside StepZen.

    To manipulate the JSON response below.

    [
      {
        "city": "Miami",
        "name": "John Doe"
      }
    ]

    This transforms argument manipulates the JSON response, applying the jq formula .[]|{name,address:{city:.city}}.

      transforms: [{pathpattern:[],editor:"jq:.[]|{name,address:{city:.city}}"}]

    To place the field, city, as a field in the new nested object, address.

    {
      "data": {
        "customers": [
          {
            "name": "John Doe",
            "address": {
              "city": "Miami"
            }
          }
        ]
      }
    }
  • jsonata:<jsonata expression> : Apply a jsonata expression to the JSON.

    Similar to the jq editor, the jsonata editor allows you to manipulate the JSON response. You can learn more about jsonata at https://jsonata.org/, and there is an online jsonata playground that you can use to test your jsonata expressions.

  • objectToArray : Transform a list of key-values pairs to an array.

    Many backends return a list of key-values pairs with field names representing the key, as opposed to an array. So one might get a response back that looks like:

    {
      "data": {
        "2333": {
          "name": "john doe",
          "age": 23
        },
        "3333": {
          "name": "jane smith",
          "age": 21
        }
      }
    }

    The classic GraphQL conversion of this will generate a new type for each entry which is not viable. The objectToArray editor addresses this issue.

    @rest (endpoint: "...", transforms: [
      {pathpattern: ["data","<>*"], editor: "objectToArray"}
    ])

    This configures StepZen to take an occurrence of zero or more keys at the path "data" and apply the transform (the expression "<>*" is the expression that you will use almost always, though other possibilities exist--see the description on pathpattern wildcard syntax above).

    This will produce:

    {
      "data": [
            {
              "name": "2333",
              "value": {
                "age": 23,
                "name": "john doe"
              }
            },
            {
              "name": "3333",
              "value": {
                "age": 21,
                "name": "jane smith"
              }
            }
          ]
    }

    Now you can treat it as an array of objects that can be processed by downstream @rest directive arguments.

  • drop : prunes the JSON tree.

    Supposing you had two queries:

    type Query {
      anonymous: [Customer]
      known: [Customer]
    }

    where in the anonymous query, you did not want the name field to be present. How would you achieve it against the same customer API? You could declare two types, type Anonymous which does not have the name field and type Customer which does, and then have each query above return those two types respectively. But an easier answer is to just prune name away from the JSON response in the first query, and as a result, there is no way it will get populated.

    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")
    }
  • rename:<old-name>,<new-name> : Rename a field.

    {
    "item": {
      "$id": 23
      }
    }

    To rename the $id field add the transforms argument below. Identify the location of the field in the pathpattern and rename the field in the editor.

      transforms: [{pathpattern: ["item"], editor: "rename:$id,id"}]

    The transformed JSON will be:

    {
      "item": {
        "id": 23
      }
    }

ecmascript

This argument enables you to run ECMAScript 5.1 scripts to manipulate the HTTP request and response bodies.

There are two function signatures you can implement:

  • bodyPOST(s) : manipulates the HTTP request body
  • transformREST(s) manipulates the HTTP response body, s.

Each function signature has a built in function get which allows retrieving arguments. Refer to the example below

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 out = JSON.parse(s);
        out.CustomMessage = get("message");
        return JSON.stringify(out);
      }
    """
  )
}

In this schema, we are sending an HTTP POST request to https://httpbin.org/anything. The payload of the request is modified using the bodyPOST function, adding an "ExtraMessage" field that contains the contents of the "message" argument. The response is modified using the transformREST function, adding a "CustomMessage" field that also contains the contents of the "message" argument.

Tip: When using ecmascript (or for that matter, any transforms), it is insightful to initially set the return type of the query to be JSON, which allows you to test and fix errors. Then you can set the return type to be a specific schema type.

Bonus Tip: You can use ecmascript to simulate a REST API response.

You can use ecmascript combined with the endpoint stepzen:empty to simulate a REST API response. See the example below:

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"}
                                  ]
                                  }))
      }
    """
  )
}

@dbquery

@dbquery (type: String!, query: String, dml: enum, table: String, configuration: String!, schema: String)

@dbquery enables you to connect a MySQL, PostgreSQL, or MSSQL database. The directive can be applied to a GraphQL query or mutation, to populate its result with data returned from a database query.

  • type is currently restricted to mysql, postgresql, mssql, and snowflake.

  • query defines the SQL statement that will be executed against the backend defined in type. The query is parameterized by the arguments to the GraphQL query that @dbquery is annotating. With a SQL query, the order of the parameter markers ? matches the order of arguments in the GraphQL query declaration. The result of the query must have column names that match the declared returned named type field names, and the types must be convertible to the declared GraphQL types. Cannot be set while dml or table are set.

  • table defines the backend table that a StepZen-generated query will be executed against. For example, with a SQL database, table:"Customers" can be specified instead of having to specify the equivalent query of SELECT id, name, email FROM Customers WHERE id = ?. Specifying table requires that the name of the table's columns matches the name of the concrete type's fields. Only one of query or table can be set.

  • configuration defines the connection details for the backend via a reference to the name in config.yaml.

  • schema is the name of the database schema and is currently restricted to postgresql and mssql.

  • dml specifies the type of a mutation on the database. Valid values are INSERT and DELETE to enable adding or removing records respectively. Cannot be used in conjunction with query. The following is an example of a type Mutation specified in an GraphQL Schema Definition Language (SDL) file that enables adding a record with dml:INSERT:

type Mutation {
  addCustomerById(id: ID!, name: String!, email: String!): Customer
    @dbquery(
      type: "mysql"
      table: "customer"
      dml: INSERT
      configuration: "mysql_config"
      schema: "schema_name"
    )
}

For examples of how to use @dbquery to connect a database and add/remove database records, see Connect a Database Using @dbquery.

@graphql

@graphql (endpoint: String!, configuration: String, headers: [{name: String, value: String}], prefix: { value: String, includeRootOperations: Boolean })

@graphql connects to a GraphQL backend. It performs an HTTP POST request when the query is executed for GraphQL backends. The directive specifies the endpoint and passes all query arguments to the backend as parameters in a URL query string. Typical arguments in an @graphql call are:

  • endpoint: Endpoint URL to be called. The variables available for constructing the URL include:
    • All arguments of the query attached to the @graphql directive.
    • All parameters in the corresponding configuration.
  • headers (optional): Header fields. Variables pass into header fields from the configuration or arguments in the query.
  • prefix (optional): Prefix to prepend to the schema type and queries. Applicable when the GraphQL generator is used to introspect the GraphQL schema. The value is indicated by the value key and the includeRootOperations boolean indicates whether root operation fields in Query and Mutation are prefixed with the value.
  • configuration: Connection details to pass into headers (e.g. authorization).

For examples of how to use the @graphql arguments, see How to Connect to a GraphQL API.

@materializer

@materializer (query: String!, arguments: [{name: "name", field: "field"}])

@materializer is used to link types (including when federating two subgraphs into a supergraph). Data from one subgraph is used to provide arguments to the same or a different subgraph, which is then used to resolve a field in the originating subgraph through a series of transformations. (For more, see Link Types: @materializer.)

@materializer defines that the annotated field is resolved by executing a field selection. The annotated field's arguments and enclosing type can be used to provide the values for arguments of the selection.

  • query: Specifies the field selection that will be used to resolve the annotated field
  • arguments: defines mappings from the annotated field's arguments or enclosing type's fields to one or more arguments of the materializer's selection (specified in the query argument). Each element of arguments consists of a name and one of the following:
    • field: "<field_name>" use <field_name> as the query argument name
    • argument: "<field_argument_name>" use <field_argument_name> as the query argument name

If an argument of the field selection is not specified, then it is mapped from a field of the enclosing type with the same name if one exists. Validation requires that all required arguments of the field selection specified in thequery argument of @materializer must be mapped using either explicit arguments via arguments or through the default of a matching field name in the enclosing type. Any optional arguments that are not mapped either explicitly or through the default will be set to their default value.

Let's take an example of two schemas that we want to combine into one. We have a Customer schema (data from a REST backend), and anOrder schema with data from a MySQL database:

customer.graphql:

type Customer {
  name: String
  id: ID
  email: String
}
type Query {
  customer(id: ID): Customer @rest(endpoint: "https://api.acme/com/customers/$id")

  customerByEmail(email: String): Customer
    @rest(endpoint: "https://api.acme.com/customers?email=$email")
}

order.graphql:

type Order {
  createOn: Date
  amount: Float
}
type Query {
  orders(customerId: ID): [Order] @dbquery(type: "mysql", table: "orders")
}

A new schema file that combines these two might look like this:

customer-orders.graphql:

extend type Customer {
  orders: [Order]
  @materializer (query: "orders", arguments: [{name: "customerId", field: "id"}]
}

where:

  • The extend clause extends a subgraph with new fields. In this case, we are adding a new field to type Customer. We can achieve the same purpose by editing the customer.graphql file, but using extend keeps the concerns of the supergraph separate from those of the subgraphs, allowing each to evolve and be maintained with minimal impact to the other.
  • The new field declaration defines the name of the new field (orders), its type ([Order]), and a @materializer directive.
  • The @materializer directive specifies a field selection that is used to resolve the annotated field. It has two arguments:
    • A field selection, specified by the query argument. This field selection must have the same type as the the new field. In this case, both have the same type [Orders]. The field selection need not be a top-level field selection. It can be a nested field selection, as long as the type of the field selection matches the type of the new field. For example, you could add a customerName: String field to a type, and populate it with a materializer like this: customerName: String @materializer(query: "customerByEmail { name }")
    • A list of argument mappings, specified by the arguments argument. Basically, you are telling StepZen how to map the arguments and fields of the annotated field and its enclosing type, respectively, to the arguments of the field selection specified by query. So here, the query: "orders" gets the customerId argument from the field id of Customer.

If you need to pass an additional argument to the query not present in the enclosing type of the annotated field, eg. ordersWeight(customerId: ID, overweight: Int!): [Order], then we would have:

extend type Customer {
  orders(vendorOverweight: Int!): [Order]
  @materializer (query: "ordersWeight",
  arguments: [{name: "customerId", field: "id"},
              {name: "overweight", argument: "vendorOverweight"}]
}

where argument maps the field argument vendorOverweight to the field selection argument overweight. There is no default mapping from the annotated field's arguments to the field selection's field arguments.

@mock

@mock mocks results type by type, so you can create mock queries in your schema. @mock ensures that any query against a type, returns mocked data based on the fields specified. This directive can be used on any type and/or interface.

@mock can be helpful for creating stubs, because you can be sure that the returned value you test will be the same. For example:

type User @mock {
  id: Int
  name: String!
  description: String
}

Next, you could make this query:

{
  users {
    id
    name
    description
  }
}

Mock data similar to the following will be returned:

{
  "data": {
    "users": [
      {
        "id": 318,
        "description": "Morbi in ipsum sit amet pede facilisis laoreet",
        "name": "Praesent mauris"
      }
    ]
  }
}

@mockfn

@mockfn can be used in conjunction with @mock to set a particular mock value.

type company {
  ceo: String @mockfn(name: "LastName")
  company_mottoes: String @mockfn(name: "List", values: [JSON])
}

In the name field, place the name of the type of data you want to mock and in the values field, place the desired values.

Here is a list of types of data you can use with @mockfn.

- `FutureDate` - select a Date up to N days into the future, where N is the first and only value in the list.
- `List` - select from the list of values. The values are converted from
  String values into the field's type.
- `NumberRange` - select a value between the first (lower) and second (upper)
  values in the list.
- `PastDate` - select a Date up to N days into the past, where N is the first and only value in the list.
- `Email` - a mocked email address
- `FirstName` - a first name
- `LastName` - a last name
- `Phone` - a phone number
- `SSN` - a mocked US social security number
- `City` - a mocked city
- `Country` - a mocked country
- `CountryCode` - a mocked country code (ISO 3166-1 alpha-2)
- `Latitude` - a mocked latitude
- `Longitude` - a mocked longitude
- `Zip` - a mocked US five digit zip code
- `UUID` - a mocked UUID
- `DomainName` - a mocked domain name
- `DomainSuffix` - a mocked domain suffix, e.g. `org`, `com`
- `IPv4Address` - a mocked IPv4 address as a string, e.g. `140.186.32.250`
- `IPv6Address` - a mocked IPv6 address as a string, e.g. `2d84:26ad:91c9:b832:42b7:55e7:bf22:e737`
- `URL` - a mocked URL
- `Username` - a mocked username
- `CreditCard` - a mocked credit card number, e.g. `2229798696491323`.
- `Currency` - a mocked currency name, e.g. `Bahamas Dollar`
- `CurrencyCode` - a mocked currency code (ISO 4217)

@sdl

@sdl(files: [#list of schemas with relative paths])

The @sdl directive is used in the index.graphql file at the root of your StepZen project. It specifies to StepZen all the .graphql files you want to assemble into a unified GraphQL schema (with relative paths). Here is an example of an index.graphql file with a list of three schemas to assemble:

schema
  @sdl(
    files: [
      "algolia/algolia.graphql"
      "contentful/blog.graphql"
      "contentful/person.graphql"
    ]
  ) {
  query: Query
}

@supplies

@supplies (query: String!)

@supplies connects a query on a concrete type to an interface query.

For example, you can use interface queries when you want to run the same query against more than one backend:

interface Delivery {
  status: String!
  statusDate: Date!
}
type DeliveryFedEx implements Delivery {
  status: String!
  statusDate: Date!
}
type DeliveryUPS implements Delivery {
  status: String!
  statusDate: Date!
}
type Query {
  delivery(carrier: String!, trackingId: String!): Delivery
  deliveryFedEx(carrier: String!, trackingId: String!): DeliveryFedEx
    @supplies(query: "delivery")
  deliveryUPS(carrier: String!, trackingId: String!): DeliveryUPS
    @supplies(query: "delivery")
}

The result of invoking the interface query delivery is the result of executing deliveryFedEx with the same arguments. This returns the data where there exists a match between UPS and FedEx IDs.

@sequence

@sequence(
  steps:
    [
      {query: "step1"}
      {query: "step2"}
    ]
)

@sequence executes multiple queries in a sequence, one after the other.

steps is an array of queries that are executed in sequence. Each object in the array must contain the name of the query that the step will call.

In the following example, @sequence is applied to the query weather. This in turn, executes the query location followed by weatherReport:

type WeatherReport {
    temp: Float!
    feelsLike: Float!
    description: String!
    units: String!
}
type Coord {
    latitude: Float!
    longitude: Float!
    city: String!
}
type Query {
    weather(ip: String!): WeatherReport
    @sequence(
      steps: [
        { query: "location" }
        { query: "weatherReport" }
      ]
    )
    location(ip: String!): Coord
    @connector(
      type: "__ipapi_location_ip_connector__"
    )
    weatherReport(latitude: Float!, longitude: Float!): WeatherReport
    @connector(
      type: "__openweathermap_weather_location_connector__"
    )
}

The following example shows the execution and response of the weather query:

{
    weather(ip:"8.8.8.8") {
        temp
        feelsLike
    }
}
{
  "data": {
    "weather": {
      "feelsLike": 19.4,
      "temp": 18.85
    }
  }
}

For additional information see Execute Multiple Queries in Sequence and How to Create a Sequence of Queries.

Executable Directives

Executable directives are used to control the execution of a GraphQL operation in a GraphQL document when requesting or mutating data. The following executable directives are supported by StepZen:

@sort

@sort

@sort is used to sort a field selection in ascending order.

Leaf fields

When applied to a field selection that is a list of a scalar or enum type (for example [String]) then @sort sorts the value of the list.

For example the result of this query:

query {
  products {
    tags # ['c', 'b', 'a', null]
  }
}

Will be transformed to this:

query {
  products {
    tags @sort # [null, 'a', 'b', 'c']
  }
}

The sort order is ascending, with null values sorted first.

Object fields

When applied to a field selection that is a list of an object or interface type then @sort sorts the list of objects.

The sort order is derived from the direct sub-selection of the @sort field selection.

For example this query will sort customers by lastName first, then firstName and then email in ascending order for each field, with null values sorted first.

query {
  customers @sort {
    lastName
    firstName
    email
  }
}

Fields can be selected but omitted from the sort order by selecting them in a fragment. This query will only sort by lastName, since firstName and email are in an inline fragment, and thus are not the direct sub-selection of customers.

query {
  customers @sort {
    lastName
    ... {
      firstName
      email
    }
  }
}