How to Connect to a REST Service

How to use a REST API as a data source for your GraphQL API

You can use StepZen to easily connect to a REST service. Follow along to see how to do it:

Get Set Up

First, you'll need a StepZen account. You'll also need the StepZen CLI to upload, deploy and test your schema on StepZen. You can install the StepZen CLI via npm.

npm install stepzen -g

Create a project folder to work in, and change the directory to that folder within your command line / console.

Plan and Create the GraphQL Type

We'll be using the Cat API to get a breed by ID.

Taking a look at the JSON that the Cat API returns, we can see that we'll need our breed.graphql file to have a type like:

type Breed {
  id: String!
  name: String!
  temperament: String!
  life_span: String!
}

There's a lot more information on the Cat API, like natural, rare, rex, origin, etc., but since id, name, temperament, and lifespan is the only information we want to from the API right now, this is all we need to define on our type. That's the beauty of GraphQL!

Ok, so that's our type, but what will our query look like?

Let's start with:

type Query {
  breedById(id: String!): Breed
}

This defines the query that will return the information for the id through the Breed type:

  • id
  • name
  • temperament
  • lifespan

But how will StepZen know which REST API we're using and what keys to use?

Enter the @rest directive!

type Query {
  breedById(id: String!): [Breed]
    @rest(
      endpoint: "https://api.thecatapi.com/v1/breeds/search?q=$id"
      configuration: "cat_config"
    )
}
  • @rest is a custom StepZen directive that connects you to a REST API. It supports PUT, POST and GET http methods.
  • endpoint tells StepZen what endpoint to call. You can see the variable $id is set with a dollar sign in the url.
  • configuration refers to your configuation setting in config.yaml. More on that below.
  • result root (not used in this example) enables you to specify an element deeper into the response structure when assigning fields.

    For example, if the API returned top level data and metadata elements, and the data we wanted was in the data element, specifying resultroot: "data" would instruct StepZen to look for data within the data element of the response. Since the Cat API returns the data object directly inside an array, we do not need to specify a resultroot.

Add Your index.graphql

You'll also need to ensure you've got a file named index.graphql in your working directory. It tells StepZen what files hold your types and queries:

schema @sdl(files: ["breed.graphql"]) {
  query: Query
}

Add Your config.yaml

Since we are using the Cat API, we will need to send your Authorization via the config.yaml.

Note: Ensure you don't commit your config.yaml - put it in .gitignore.

configurationset:
  - configuration:
      name: cat_config
      Authorization: Apikey MY_PERSONAL_ACCESS_TOKEN

Query the API

Now you're ready to run stepzen start. You should see your query editor pop up in the browser!

Run this query by copying/pasting and hitting the play button:

query MyQuery {
  breedById(id: "abys") {
    id
    life_span
    name
    temperament
  }
}

Your result will look like:

REST

Work With Nested JSON

Let's take a closer look at the first result from the JSON formatted data returned by the Cat API:

[
  {
    "adaptability": 5,
    "affection_level": 5,
    "alt_names": "",
    "cfa_url": "http://cfa.org/Breeds/BreedsAB/Abyssinian.aspx",
    "child_friendly": 3,
    "country_code": "EG",
    "country_codes": "EG",
    "description": "The Abyssinian is easy to care for, and a joy to have in your home. They’re affectionate cats and love both people and other animals.",
    "dog_friendly": 4,
    "energy_level": 5,
    "experimental": 0,
    "grooming": 1,
    "hairless": 0,
    "health_issues": 2,
    "hypoallergenic": 0,
    "id": "abys",
    "image": {
      "height": 1445,
      "id": "0XYvRd7oD",
      "url": "https://cdn2.thecatapi.com/images/0XYvRd7oD.jpg",
      "width": 1204
    },
    "indoor": 0,
    "intelligence": 5,
    "lap": 1,
    "life_span": "14 - 15",
    "name": "Abyssinian",
    "natural": 1,
    "origin": "Egypt",
    "rare": 0,
    "reference_image_id": "0XYvRd7oD",
    "rex": 0,
    "shedding_level": 2,
    "short_legs": 0,
    "social_needs": 5,
    "stranger_friendly": 5,
    "suppressed_tail": 0,
    "temperament": "Active, Energetic, Independent, Intelligent, Gentle",
    "vcahospitals_url": "https://vcahospitals.com/know-your-pet/cat-breeds/abyssinian",
    "vetstreet_url": "http://www.vetstreet.com/cats/abyssinian",
    "vocalisation": 1,
    "weight": {
      "imperial": "7  -  10",
      "metric": "3 - 5"
    },
    "wikipedia_url": "https://en.wikipedia.org/wiki/Abyssinian_(cat)"
  }
]

As you can see, there are cases where the data is nested inside objects, like the imperial weight:

"weight": {
  "imperial": "7  -  10",
  "metric": "3 - 5"
},

How would we get that data? We'll use a parameter on the @rest directive setters:

type Breed {
  id: String!
  name: String!
  temperament: String!
  life_span: String!
  origin: String!
  imperial_weight: String
}

type Query {
  breedById(id: String!): Breed
    @rest(
      endpoint: "https://api.thecatapi.com/v1/breeds/search?q=$id"
      configuration: "cat_config"
      setters: [{ field: "imperial_weight", path: "weight.imperial" }]
    )
}

Note: StepZen performs a "smart" inspection on these setters so we don't need the resultroot anymore.

But what if we didn't want to rename the field? What if we wanted our GraphQL response to look just like the REST response?

To do that you create a new type for the nested data. StepZen automatically resolves the nodes correctly.

Let's look at an example. Let's modify our Breed type to include the weight data as-is from the response. First, we need to create a type for that data. Let's call it Weight and add it alongside our Breed type. Then, we simply modify the Breed type to include an attribute of type Weight named weight:

type Weight {
  imperial: String!
  metric: String!
}

type Breed {
  id: String!
  name: String!
  temperament: String!
  life_span: String!
  origin: String!
  imperial_weight: String
  weight: Weight
}

That's all we need to change. StepZen will automatically resolve the nested types for you.

Connect Two REST APIs

We can see the country the cat belongs to under "origin". What if we wanted details on that country, coming from another API? Let's use the Rest Countries API to set up a query first.

Here's our country.graphql:

type Country {
  name: String!
  region: String!
  subregion: String!
  population: Int!
}

type Query {
  countryByName(country: String!): Country
    @rest(
      endpoint: "https://restcountries.eu/rest/v2/name/$country?fullText=true"
      configuration: "countries_config"
      resultroot: "[]"
    )
}

Add it to our index.graphql like this:

schema @sdl(files: ["breed.graphql", "country.graphql"]) {
  query: Query
}

Now a query will looks like this:

query MyQuery {
  countryByName(country: "aruba") {
    name
    population
    region
    subregion
  }
}

The very will return the following:

{
  "data": {
    "countryByName": {
      "name": "Aruba",
      "population": 107394,
      "region": "Americas",
      "subregion": "Caribbean"
    }
  }
}

Now, how do we get that information to come back from our breedById query? Enter another custom StepZen directive, @materializer!

We add this line:

@materializer (query: "countryByName", arguments: [{name: "country", field: "origin"}])

query tells StepZen what query to use, and the arguments fed in are what parameter countryByName will use such as name, field. The value of those parameter will be as supplied by the breedById query.

You can put it right under the closing bracket:

type Breed {
  id: String!
  name: String!
  temperament: String!
  life_span: String!
  origin: String!
  imperial_weight: String
  catCountry: Country
    @materializer(
      query: "countryByName"
      arguments: [{ name: "country", field: "origin" }]
    )
}

type Query {
  breedById(id: String!): Breed
    @rest(
      endpoint: "https://api.thecatapi.com/v1/breeds/search?q=$id"
      configuration: "cat_config"
      setters: [
        { field: "id", path: "id" }
        { field: "name", path: "name" }
        { field: "origin", path: "origin" }
        { field: "temperament", path: "temperament" }
        { field: "life_span", path: "life_span" }
        { field: "imperial_weight", path: "weight.imperial" }
      ]
    )
}

This is an example of the corresponding query:

query MyQuery {
  breedById(id: "abys") {
    catCountry {
      name
      population
      region
      subregion
    }
    id
    imperial_weight
    life_span
  }
}

The query returns:

{
  "data": {
    "breedById": {
      "catCountry": {
        "name": "Egypt",
        "population": 91290000,
        "region": "Africa",
        "subregion": "Northern Africa"
      },
      "id": "abys",
      "imperial_weight": "7  -  10",
      "life_span": "14 - 15"
    }
  }
}

For more on @materializer, see our docs on linking types.

Handle JSON with Special Values

JSON supports field names with special characters like #.

last.fm is an API that uses JSON names like #text.

To ensure that StepZen can parse the data, put backticks around the field in your setters path, similar to this example:

type lastFMTracks {
  Name: String
  Artist: String
  Album: String
  Image: String
  NowPlaying: Boolean
  URL: String
}
​
type Query {
  lastFMTracksByUserId(id:ID!): [LastFmTracks]
    @supplies(query:"tracksByUserId")
    @rest(
      setters: [
        {field: "Artist", path: "recenttracks.track[].artist.`#text`"}
        {field: "Album", path: "recenttracks.track[].album.`#text`"}
        {field: "Name", path: "recenttracks.track[].name"}
        {field: "Image", path: "recenttracks.track[].image[2].`#text`"}
        {field: "NowPlaying", path: "recenttracks.track[].`@attr`.nowplaying"}
        {field: "URL", path: "recenttracks.track[].url"}
      ]
      endpoint: "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=$id;&api_key=$apikey;&format=json"
      configuration: "LastFm"
  )
​
}

That way, you can retrieve JSON with symbols.

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.