Execute Multiple Sequential GraphQL Queries with the @sequence Directive

Anthony CampoloStepZen's custom directive @sequence allows for multiple queries to be executed in a sequence, one after the other to return a single result. Each step in the sequence passes data to the next, allowing data from step 1 in a sequence to be used as arguments to step 2 in a sequence and so on.
This can allow you to create complex queries without having to manually orchestrate APIs, write lots of server-side logic, or handle asynchronous calls and database queries. In this example we will see how to use @sequence to query three APIs to get the sunrise based on the users's location (latitude and longitude) which we get using their IP address.
You can view the code for this example over on our StepZen Github collection.
The steps argument
The only required argument to create a sequence is the steps that make up that sequence. This is an array of objects that the query steps through. Each object in the array must contain at a minimum the name of the query that the step will call.
@sequence(
steps: [
{ query: "step1" }
{ query: "step2" }
]
)
The value of query must be a query that is defined on the schema. The result of the sequence will be the same as the result of the last step, step2 in this case.
Collecting results
By default, the result of the final step is the result of the sequence. However, in some scenarios, you may need data from prior steps as part of the result of the entire sequence.
For example, imagine step1 returns user information including their name but step2 returns location information including city. If you wanted the full sequence to return both name and city, you can use an extra step in the sequence that calls a query utilizing a special echo connector.
@sequence(
steps:
[
{ query: "step1" }
{ query: "step2" }
{ query: "collect" }
]
)
The collect query in step 3 of the sequence would then use the echo connector:
collect (
name: String!,
city: String!
): UserWithLocation
@connector (
type: "echo"
)
Sunrise API Example
We'll use three APIs to get the sunrise based on the location (latitude and longitude) which we get using the IP address:
This requires executing a sequence of steps:
- Get the
locationgiven an IP address. - Get
sunrisefrom thelatitudeandlongitudederived from thelocation.
Creating the Query
Let's start by writing the GraphQL code that will create the schema for our StepZen endpoint. Create a file sunrise.graphql in your working directory that contains the following types:
SunriseSunsetcontains properties about the sunrise and sunset
type SunriseSunset {
sunrise: String!
sunset: String!
solar_noon: String!
day_length: String!
civil_twilight_begin: String!
civil_twilight_end: String!
nautical_twilight_begin: String!
nautical_twilight_end: String!
astronomical_twilight_begin: String!
astronomical_twilight_end: String!
sunrise_unix: Int
sunset_unix: Int
abbreviation: String
}
Geolocationcontains properties about the location (i.e. longitude and latitude)
type Geolocation {
ip_address: String!
city: String
city_geoname_id: Int
region: String
region_iso_code: String
region_geoname_id: Int
postal_code: String
country: String
country_code: String
country_geoname_id: Int
country_is_eu: Boolean
continent: String
continent_code: String
continent_geoname_id: Int
longitude: Float
latitude: Float
abbreviation: String
}
Timezonecontains properties about the timezone
type Timezone {
countryCode: String
countryName: String
zoneName: String!
abbreviation: String
gmtOffset: Int!
dst: Int
zoneStart: Int
zoneEnd: Int
nextAbbreviation: String
timestamp: Int
timestring: String
formatted: Date
}
We've also defined four queries.
getLocationByIpAddressgets a location using IP Geolocation API based upon a provided IP address
type Query {
getLocationByIpAddress(
ip_address: String!
): Geolocation
@rest(
endpoint: "https://ipgeolocation.abstractapi.com/v1/?api_key=$api_key&ip_address=$ip_address"
configuration: "abstractapi_config"
)
}
getTimezoneByLatLonggets the timezone from TimeZoneDB based upon a provided latitude and longitude
type Query {
getTimezoneByLatLong(
latitude: Float!,
longitude: Float!
): Timezone
@rest(
endpoint: "http://api.timezonedb.com/v2.1/get-time-zone?format=json&key=$api_key&by=position&lat=$latitude&lng=$longitude"
configuration: "timezone_config"
)
}
getSunriseByLatLonggets the sunrise from the Sunrise Sunset API based upon a provided latitude and longitude
type Query {
getSunriseByLatLong(latitude: Float!, longitude: Float!): SunriseSunsetFoo
@rest(
endpoint: "https://api.sunrise-sunset.org/json?lat=$latitude&lng=$longitude"
resultroot: "results"
cel: """
function transformREST(json)
{ ... }
"""
)
}
We are using a custom transformation that StepZen allows within the cel property. This is an undocumented beta capability of StepZen that allows you to write custom code to process the results of a query.
4. collectSunrise uses the echo connector to return the final result
type Query {
collectSunrise(
sunrise: String!
sunset: String!
solar_noon: String!
day_length: String!
civil_twilight_begin: String!
civil_twilight_end: String!
nautical_twilight_begin: String!
nautical_twilight_end: String!
astronomical_twilight_begin: String!
astronomical_twilight_end: String!
sunrise_unix: Int
sunrise_local: String
sunset_unix: Int
sunset_local: String
abbreviation: String
): SunriseSunset
@connector(type: "echo")
}
getSunriseByIp sequence
Now if we look at our complete getSunriseByIp sequence we will see an array of steps that will execute in order to get our intended result.
getSunriseByIp(ip_address: String!): SunriseSunset
@sequence(
steps: [
{ query: "getLocationByIpAddress" }
{ query: "getTimezoneByLatLong" }
{ query: "getSunriseByLatLong" }
{ query: "collectSunrise" }
]
)
index.graphl
To test this out, create an index.graphql with the following content to tell StepZen how to assemble our schema. The index.graphql file ties the schema files together so that StepZen can deploy them to one endpoint.
schema
@sdl(
files: [
"sunrise.graphql"
]
) {
query: Query
}
The entire project is consolidated down to one file to simplify the example, but in practice it is good to break up your files into specific concerns such as Sunrise, Geolocation, and Timezone.
Deploy the schema with stepzen start
Deploy the schema using the stepzen start command and issue the following query in the StepZen Schema explorer. You can find your own IP address by Googling, "get my IP address."
query MyQuery {
getSunriseByIp(ip_address: "2601:643:8500:abc0:3d25:c4aa:6445:d586") {
abbreviation
sunrise
sunset
}
}
The response in the browser will be:
{
"data": {
"getSunriseByIp": {
"abbreviation": "PDT",
"sunrise": "6:04:56 AM",
"sunset": "8:25:52 PM"
}
}
}
Let's see how this works in more detail.
How @sequence Works
A sequence is a set of steps, and in StepZen's @sequence directive, each step is either a query or a mutation. So, in this fragment:
getSunriseByIp(ip_address: String!): SunriseSunset
@sequence(
steps: [
{ query: "getLocationByIpAddress" }
{ query: "getTimezoneByLatLong" }
{ query: "getSunriseByLatLong" }
{ query: "collectSunrise" }
]
)
@sequence tells StepZen to execute the following queries in order:
getLocationByIpAddressgetTimezoneByLatLonggetSunriseByLatLongcollectSunrise
Each query in the sequence takes some parameters and returns a type. For example, getLocationByIpAddress(ip_address: String!): Geolocation takes ip_address and returns type Geolocation. For the first query in the sequence, the input parameters come from the input parameters of the overall query.
For the following steps, the parameters can come from either the overall query or from any of the previous steps. If there is a name conflict, then StepZen picks the step that is closest to the current one being executed.
The second query, getTimezoneByLatLong, requires latitude and longitude as its parameters. The getLocationByIpAddress query returns a type of Geolocation that has latitude and longitude fields, so that is the query that provides the fields to getTimezoneByLatLong.
StepZen automatically populates the query parameters in getTimezoneByLatLong with the values returned by getLocationByIpAddress. If any step in the sequence returns an array of results then the next step is called once for every entry in that list. You can think of it like executing a for loop on the array of results.
The final response returned by the query is the output of the last step. Therefore, since the collectSunrise query in our last step returns a type of SunriseSunset, our sequenced getSunriseByIp query must do the same.
Conclusion
We've now seen how we can easily and quickly combine multiple queries together with StepZen's @sequence directive. If you want to explore more example repositories you can visit the StepZen GitHub.


