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. The following topics describe how to perform this:

Get Set Up

Follow the steps below to get set up:

  1. Ensure you have a StepZen account.
  2. Install the StepZen CLI via npm which you'll need to upload, deploy, and test your schema on StepZen:
npm install stepzen -g
  1. Create a project directory to work in, and navigate into that directory using your command line / console.
  2. Perform the steps in the following subsections to complete the set-up process:

Plan and Create the GraphQL Type

This tutorial uses the Cat API to get a cat breed by ID.

Based on the JSON that the Cat API returns, your breed.graphql file needs to define a type similar to the following:

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

There's a lot more information provided by the Cat API like natural, rare, rex, origin, etc. Since id, name, temperament, and lifespan are the only fields needed from the API for this tutorial right now, this is all that is required to define on the type. That's the beauty of GraphQL!

So that's the type, but what will the query look like?

Start with:

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

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

  • id
  • name
  • temperament
  • lifespan

But how will StepZen know which REST API is used and what keys to use?

Enter @rest, a custom StepZen directive that connects you to a REST API. It supports PUT, POST, and GET HTTP methods:

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

The following are the fields for @rest:

  • endpoint (required): A string containing the URL of the endpoint that StepZen is to invoke. The value can contain variables preceded by a $ which will be replaced by StepZen. These variables can match query arguments or configuration variables of the same name. For example, a variable named $username will be replaced by the value of a username query argument. In the code example above, the variable $id in the URL will be replaced by an id query argument.

Note: If there is no clear boundary for $var, the variable substitution may fail. For example if you have an endpoint such as endpoint: "https://site/path/portion$var1/?fiter=$var2" then $var2 will be replaced, but $var1 will remain as $var1 in the URL (since it's embedded into the path "portion"). To enable StepZen to recognize it as a variable and correctly replace it, add a ; at the end of the variable (e.g. the new endpoint will look like: endpoint: "https://site/path/portion$var1;/?filter=$var2").

  • configuration: Refers to your configuration setting in config.yaml - more on that below.
  • result root (not used in this example): Specifies 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 you want is in the data element, specifying resultroot: "data" instructs StepZen to look for data within the data element of the response. Since the Cat API returns the data object directly inside an array, you do not need to specify a resultroot.

Add Your index.graphql

You must also create a file named index.graphql in your working directory to tell StepZen what files hold your types and queries:

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

Add Your config.yaml

For the Cat API, send your Authorization via the config.yaml file:

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

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

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 the following and pressing play:

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 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 you get that data? 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: 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 backticks. 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" }

The value accepts template literals, but the field will not. For example, { field: "terminal", path: "terminal-name" } will not work.

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

What if you don't want to rename the field? What if you want your GraphQL response to look just like the REST response?

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

Let's look at an example. Modify the Breed type to include the weight data as-is from the response:

  1. Create a type for that data called Weight and add it alongside the Breed type.
  2. 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 you need to change. StepZen will automatically resolve the nested types for you.

Connect Two REST APIs

The cat's country is specified in origin. What if you wanted details on that country, coming from another API? Let's use the Rest Countries API to set up a query first.

Here's the country.graphql file:

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 index.graphql like this:

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

Now the query looks like this:

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

The query will return the following:

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

Now, how do you return that information from the breedById query? Enter another custom StepZen directive, @materializer!

Add the following line:

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

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

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.

Use @rest with a Mutation

The @rest directive can also be used with Mutations.

The following example shows how to add a Mutation that will send an email via SendGrid's API:

type Mutation {
  sendTemplatedEmail(to: String!, from: String!, template_id: String!): Email
}

Mutation is a POST method to the endpoint: https://api.sendgrid.com/v3/mail/send. We need to send the body through the postbody field that follows the template email example in the SendGrid docs. The configuration is set to the sendgrid_config that we defined in our config.yaml, to securely send the API key.

The postbody can seem overwhelming. We are URL-encoding the three arguments (to, from, and template_id) into the fields expected by the REST API. We use a \"{{.Get \"from\"}}\" structure to properly add the quotes around the values.

type Mutation {
  sendTemplatedEmail(to: String!, from: String!, template_id: String!): Email
    @rest(
      method: POST
      endpoint: "https://api.sendgrid.com/v3/mail/send"
      postbody: "{\"personalizations\": [{\"to\": [{\"email\": \"{{.Get \"to\"}}\"}]}],\"from\": {\"email\": \"{{.Get \"from\"}}\"},\"subject\":\"demo\",\"content\": [{\"type\": \"text/html\",\"value\": \"Heya!\"}], \"template_id\" : \"{{.Get \"template_id\"}}\"}"
      configuration: "sendgrid_config"
    )
}

Send an Email using sendTemplatedEmail

Now that the mutation is written, let's test it out within the GraphiQL editor that was launched by stepzen start:

mutation MyMutation {
  sendTemplatedEmail(
    from: "example@stepzen.com"
    template_id: "d-6d5704f9a5c9424d9c67a226c8fa2b43"
    to: "example@stepzen.com"
  ) {
    message
  }
}

You can see in the response below that SendGrid does not send a success message in the response. For now, we're just returning mock data. If you want to set up response data, set up a webhook on SendGrid.

{
  "data": {
    "sendTemplatedEmail": {
      "message": "Vestibulum lacinia arcu eget nulla"
    }
  }
}

Even though we are getting mock data in the response, we'll still see the email in the recipients inbox, that was sent to the to field in the mutation!

email_in_inbox

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.