Custom GraphQL Directives Reference

Core structure and information you need to write your SDL Code

SDL Code & Directives

A schema is a GraphQL Schema Definition Language (SDL) file. The following represents the core structure and information needed to write your SDL Code.

  1. Create a dir sdl-dir and create all your SDL files (xxxx.graphql) inside.

You must also create an index.graphql in the root of that directory, with the following structure:

schema @sdl(
  files: [
    # all files you want included, with relative path
  ]
) {
  query: Query
}

You can use multiple files to organize your SDL, such as separating interfaces from concrete types. You can also separate domains this way. If you have types that will be used to query a social media endpoint as well as types that will be used to query a weather endpoint, here is one possible example of your SDL:

schema @sdl(
  files: [
    "social.graphql",
    "weather.graphql"
  ]
) {
  query: Query
}
  1. Create a GraphQL type to expose your chosen fields. For example:
type WeatherReport {
  latitude: Float!
  longitude: Float!
  description: String
  temp: Float
}

Directives

A directive is an identifier preceded by the @ character, optionally followed by a list of named arguments, which can appear after almost any form of syntax in the GraphQL query or schema languages. This section describes StepZen directives and their application when building and running your GraphQL API on StepZen.

You can configure a directive using a variable substitution. Variables can come from:

  • The configuration.
  • Parameters passed into the query.

To learn more about directives, see the GraphQL draft specification.

@rest

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

For REST backends, execution of the query results in an HTTP GET call using the specified endpoint and the query arguments as URL query parameters. The typical parameters in an @rest call are:

  • endpoint: The URL to be called. All parameters of the query this @rest is attached to, as well as all parameters in the corresponding configuration are available for constructing the URL.
  • resultroot: The root where all the data you need for the concrete type that the call feeds into. Default is the root of JSON. It is optional.
  • setters: The optional renaming of fields from JSON into the fields of the concrete type. Only fields that need to be remapped need to be specified, otherwise StepZen makes good default assumptions.
  • configuration: The connection details to pass down into headers. For example, authorization.

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

The Endpoint URL

The complete rendered URL is URL Encoded. The endpoint has the form: <proto>://<userinfo>@<host>.<domain>/<pathelt>/<pathelt>/...?<query>

  • <proto> must be explicit and can be any legal value.
  • <userinfo> is <username>:<password> and both of these can be wholly-substituted by a variable provided in the config only. No partial subs and no values from the query parameters are allowed.
  • <host> can be partially substituted but will take values only from the config, not the query parameters.
  • <domain> is not substitutable and has to be explicit.
  • <pathelt> and <query> are partially substitutable and can take values from either the query parameters or from the config.
  • Variables can be introduced to the endpoint in the form $var (Like $username in the example we used.)

Wholly-substitutable means that all of it is either explicit or substituted. Partially substitutable means something like account-$account is allowed, and if the variable account = 2 then this renders as account-2.

Any value in the query parameters or config that is not a string is auto-coerced to string. Therefore floating point values, int values, boolean values, timestamp values, etc., will get auto-coerced to string using a standard coercion template.

The Query Response

The query response must be a JSON array of objects if the query is declared to return a list [X] - or a single object otherwise. The returned objects must match the JSON representation of the concrete type declared as the query return named type.

For example:

type Article {
id: ID!
title: String!
description: String

type Query {
myArticles(username: String!): [Article]
 @rest(
   endpoint: "https://dev.to/api/articles?username=$username&per_page=1000"
 )
}
}

The following GraphQL query placed in your graphiql query editor results in a HTTP GET request to https://dev.to/api/articles?username=$username&per_page=1000.

query MyQuery {
  myArticles(username: "remotesynth") {
    title
    description
  }
}

Response:

{
  "data": {
    "myArticles": [
      {
        "description": "Create a developer portfolio featuring content pulled from your DEV.to blog posts and GitHub profile and projects using Next.js and StepZen.",
        "title": "Creating a Developer Portfolio using Next.js, GraphQL, DEV and GitHub"
      },
      {
        "description": "Some tips and things to think about when choosing to run a virtual developer conference based upon my recent experiences.",
        "title": "Tips for Running Your First Virtual Conference"
      }
    ]
  }
}

See our blog post on turning a REST API into GraphQL for more information about this example.

If the request needs to return no results, then the response can be any of the following:

  • JSON null
  • Empty response
  • 204 status code
  • Empty list for a query returning a list

For your config.yaml, you must have an authorization header to avoid 404s.

An authorization header can be set by the configuration entry authorization in the named configuration. For example:

- configuration:
      name: dev_config
      Authorization: Bearer MY_PERSONAL_ACCESS_TOKEN

@dbquery

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

  • type is currently restricted to mysql and postgresql.

  • query defines the query 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 an 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.

  • 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 the name of the table's columns exactly match 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.

  • dml specifies the type of a mutation on the database. Valid values are INSERT and DELETE to enable adding or removing records. The following is an example of a type Mutation specified in an 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"
    )
}

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

@materializer

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

The following are the parameters for @materializer:

  • query: Specifies which query will be used.
  • arguments: Arguments that will be used in the query. name is the name of an argument and field is the value of the argument.

This syntax tells the parent type how the child field is materialized.

The example below materializes the orders field in type Customer. It does this by issuing the orders query and passing in the value of the Customer type's id field for the customerId:

type Customer {
    id: ID!
    name: String!
    email: String
    customerOrders: [Order]
    @materializer (query: "customerOrders",
                   arguments: [{name: "customerId", field: "id"}])
    }
    ...
    type Query {
    ...
    orders (customerId: ID!): [Order]
}

For more examples of how to use @materializer with a Customer type see Link Types.

arguments defines one or more arguments to pass to the query. Each argument consists of a name and one of the following:

  • argument: "<argument_value>" - Sets the argument to the specified value.
  • field: "<field_value>" - Sets the field to the specified value.

StepZen makes default arguments. For example, because weatherReport took exactly two parameters: latitude and longitude and their value at runtime is the same as the fields latitude and longitude of type Location.

Note: If an argument such as longitudeName is not listed in arguments then:

  • It is equivalent to {name: "longitudeName" field:"longitude"} if the field longitude exists in the enclosing type.
  • Otherwise the query argument must have a default value. For example, longitudeName:Int!=10 or be nullable.
type Location {
    city: String
    country: String!
    currency: String
    ip: String!
    latitude: Float!
    longitude: Float!
    postalCode: String
    weather: WeatherReport @materializer(query: "weatherReport")
}

@mock

This directive can be specified against any type and/or any interface. A @mock on a type ensures that any query that is against that type will return mocked data based on the fields you specify. For example:

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

Next, you could make this query:

{
  users {
    id
    name
    description
  }
}

The following mock data would be returned:

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

@connector

If you ran stepzen import ipapi and you then see @connector in yourlocationIpApi.graphql`:

type Query {
  location(ip: String!): Location
    @connector(
      type: "__ipapi_location_ip_connector__"
      configuration: "ipapi_default"
    )
}

The connector exposes a fixed StepZen schema for you to use. In this case, it is a schema that calls the IP API with a GraphQL query.

The type determines the fixed schema the Query connects to, and the configuration refers to the default also set up by StepZen.

You can subset the fields from that schema, but you cannot change the names of the fields or their types.

Here is a list of connector types based on the 'Backends':

FedEx

  • Arguments: "fedex"
  • Returned Interface: (carrier: String, trackingId: String!):Delivery

UPS:

  • Arguments: "ups"
  • Returned Interface: (carrier: String, trackingId: String!):Delivery

OpenWeatherMap

Example 1

  • Arguments: "__openweathermap_weather_locationdate_connector__"
  • Returned Interface: (date: Date!, latitude: Float!, longitude: Float!):Weather

Example 2

  • Arguments: "__openweathermap_weather_location_connector__"
  • Returned Interface: (latitude: Float!, longitude: Float!):WeatherReport

AccuWeather

  • Arguments: "accuweather"
  • Returned Interface: (date: Date!, latitude: Float!, longitude: Float!):Weather

IP-API

  • Arguments: "__ipapi_location_ip_connector__"
  • Returned Interface: (ip: String!):Location

HolidayAPI

  • Arguments: "__holiday_api_holiday_location_year_month_connector__"
  • Returned Interface: (country: String!, month: Int!, year: Int!):[Holiday]

If you want to personalize your configuration, you can change the configuration name to match something that you create in a config.yaml. For further detail see Backend Configurations.

@sdl

@sdl(files: [#list of schemas with relative paths]) The @sdl directive is used as part of an 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 path). Here is an example of an index.graphql file with a list of 3 schemas to assemble:

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

@supplies

@supplies (query: String!): This directive 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")
    @connector(type: "fedex", configuration: "fedex_default")
  deliveryUPS(carrier: String!, trackingId: String!): DeliveryUPS
    @supplies(query: "delivery")
    @connector(type: "ups", configuration: "ups_default")
}

This means that the result of invoking the interface query delivery is the result of executing deliveryFedEx with the same arguments, giving a full return of the data where there exists a match between UPS and FedEx IDs.

This site uses cookies: By using this website, you consent to our use of cookies in accordance with our Website Terms of Use and Cookie Policy.