API Composition with StepZen and Kong
Michael Heap

By on September 15, 2022

API Composition with StepZen and Kong

There are many pros and cons for both GraphQL and REST APIs, but one of the areas where GraphQL really shines is API composition. Taking data from multiple APIs and combining them to make something new is a key part of delivering a useful service. 

Today, we’re going to take a look at StepZen, a product that allows you to build GraphQL APIs composed of REST, Database, and other GraphQL sources. We’ll use it to build an API endpoint that shows the price of an item in a user’s local currency based on their IP address. GraphQL remains an emerging technology, and adoption is still lagging behind RESTful APIs. Kong provides the degraphql plugin which allows us to convert the GraphQL API we build with StepZen back into a REST API, which may be more comfortable for users of our API.

API Composition with StepZen

StepZen provides a GraphQL studio that helps you build and deploy your own GraphQL API by combining prebuilt APIs such as Accuweather, Airtable, GitHub, SpaceX, Trello, and more. In this example, we’re going to combine IP-API (to convert an IP address to a geolocation) with the Frankfurter API (an exchange rate API). GraphQL studio helpfully provides some prebuilt queries to help us understand how these APIs work and how they can be combined.

The schema tab in GraphQL Studio allows you to see the data available from each API. I’d like to call your attention to the priceInCountry field in the IpApi_Location type. priceInCountry is defined as a materializer, which is a custom StepZen directive that acts like a function. priceInCountry takes arguments provided by the caller and combines them with the currency field in the current schema by calling the frankfurter_convertedAmount query to calculate a local price. 

priceInCountry(amount: Float!, from: String!): Float
    @materializer(
      query: "frankfurter_convertedAmount"
      arguments: [
        { name: "to", field: "currency" }
        { name: "amount", argument: "amount" }
        { name: "from", argument: "from" }
      ]
    )

The materializer definition is included in the schema itself inside type Query, and uses JavaScript to call an API and transform the data:

frankfurter_convertedAmount(
    amount: Float!
    from: String!
    to: String!
): Float
    @rest(
      cel: """
      function transformREST(s) {
          var data = JSON.parse(s)
          let toCurrency = get('to')
          let amount = get('amount')
          let convertedAmount = (data["rates"][toCurrency]||1.00) * amount

          return JSON.stringify(convertedAmount)
      }
      """
      endpoint: "https://api.frankfurter.app/latest?from=$from"
    )

We’re going to use this materializer to build a query that accepts an amount, source currency, and IP address and returns the identified country code and price in that country:

query ($amount: Float!, $from: String!, $ip: String!) {
  converted:ipApi_location(ip: $ip) {
    countryCode
    price:priceInCountry(amount: $amount, from: $from)
  }
}

We’ll walk through how to run this query in the next section, but first, we need to deploy our new API.

Deploying your API

GraphQL Studio is great, but it’s not intended for production deployments. To ship our API, we need to import it as a data source into our own StepZen account using the CLI. The GraphQL endpoint URL is available in the top bar in GraphQL Studio.

To do this, we run stepzen import graphql to tell StepZen that we want to build a new API on top of an existing GraphQL API. This will generate some schema files locally based on the GraphQL endpoint URL that you provide.

stepzen import graphql

? What would you like your endpoint to be called? demo/converter
? What is the GraphQL endpoint URL? https://graphqldd.stepzen.net/api/dd1cf47f51ac830fe21dc00ec80cee65/__graphql
? Prefix to add to all generated type names (leave blank for none)
? Add an HTTP header, e.g. Header-Name: header value (leave blank for none)

Starting... done

Successfully imported schema graphql from StepZen

Once the import completes, you can deploy your API to StepZen by running stepzen start. At this point you can visit http://localhost:5001/demo/converter to test your deployed API using the locally running StepZen query explorer. Paste the query definition above into the main editor section, and then paste the following into the Query Variables editor:

{
  "amount": 100,
  "from": "GBP",
  "ip": "8.8.8.8"
}

Submit your request and you’ll see a response in the right-hand pane that looks like this:

{
  "data": {
    "converted": {
      "countryCode": "US",
      "price": 115.69
    }
  }
}

When I ran stepzen start, one of the messages provided me with the URL of my new API:

Your API URL is https://hamburg.stepzen.net/demo/converter/__graphql

However, if I visit that URL I can see that my requests need to be authenticated. We want to make this a public API, but only if it’s accessed through our API gateway. We can automatically add an authentication header, but first, we need to run Kong Gateway.

Running Kong Gateway

The quickest way to get started with Kong Gateway is to run the following command (you’ll need docker installed):

curl -Ls https://get.konghq.com/quickstart | sh -s -- -i kong-gateway

This will create two Docker containers – a database to store configuration and Kong Gateway itself. Once you see the “✔ Kong is ready!” message, we can configure a route that proxies requests through to StepZen.

> Port 8001 is the administration port for Kong Gateway, while port 8000 is used to proxy requests to their destination

curl localhost:8001/services -d name='graphql-demo' -d url='https://hamburg.stepzen.net/demo/converter/__graphql
curl localhost:8001/services/graphql-demo/routes -d paths='/price'

If you now call curl localhost:8000/price you’ll see the same 401 Unauthorized error that we saw when calling the API directly.

Automatic API Authentication

Now Kong Gateway is proxying requests to our StepZen API, we can automatically add authentication credentials to requests flowing through the gateway. We’ll use the Request Transformer plugin in Kong to add the `Authorization` header to the incoming request before forwarding it to StepZen. 

First, run stepzen whoami –apikey to fetch your StepZen credentials. Configure the Request Transformer plugin with the StepZen key using the config.append.headers setting:

curl localhost:8001/services/graphql-demo/plugins -d name='request-transformer' --data-urlencode config.append.headers='Authorization: Apikey YOUR_KEY_HERE'

Once you’ve run the above command, you should be able to send a GraphQL query to Kong Gateway and receive a response from StepZen:

curl localhost:8000/price \
   --header "Content-Type: application/json" \
   --data '{"query": "query ($amount:Float!,$from:String!,$ip:String!){converted:ipApi_location(ip:$ip){countryCode price:priceInCountry(amount:$amount,from:$from)}}", "variables": {"amount":100,"from":"GBP","ip":"8.8.8.8"}}'

{
  "data": {
    "converted": {
      "countryCode": "US",
      "price": 115.69   
    }
  }
}

At this point, we’re using Kong Gateway to proxy requests to StepZen and add authentication. It also means it’s easy to add any other plugin to protect your API as the number of requests grows.

Everything we’ve done up until now can be done with the free version of Kong, but we’re about to move on to some enterprise features. If you want to code along, make sure to apply your license to your locally running instance:

export KONG_LICENSE_DATA=’{“my”:”license”}’
curl -X POST localhost:8001/licenses -d payload=$KONG_LICENSE_DATA

Convert GraphQL to REST

As I mentioned earlier, our existing customers are used to consuming REST APIs and aren’t too familiar with how to write GraphQL queries. In order to keep things simple for them, we’ll use Kong Gateway to convert our StepZen GraphQL API to a REST API using the degraphql plugin.

The degraphql plugin allows you to create traditional REST-based routing rules, map them to GraphQL queries, and forward the queries to a server. Before installing the plugin, we need to modify the existing graphql-demo service, setting the target URL to the domain-only portion of our StepZen URL.

curl -X PATCH localhost:8001/services/graphql-demo -d url='https://YOUR_STEPZEN_URL_DOMAIN'

Once this is done, we can activate the degraphql plugin on our service by providing the path to our demo endpoint in the graphql_server_path configuration option:

curl localhost:8001/services/graphql-demo/plugins -d name='degraphql' -d config.graphql_server_path='/demo/converter/__graphql'

Once degraphql is activated, requests to our/price endpoint will return a 404 response rather than proxying to the upstream server. This is because we haven’t mapped any REST requests to an underlying GraphQL query yet.

To map a path to a query, we can make a POST request to /degraphql/routes:

curl -X POST http://localhost:8001/services/graphql-demo/degraphql/routes \
--data uri="/gbp/:ip/:amount" \
--data query='query($amount:Float!,$ip:String!){converted:ipApi_location(ip:$ip){countryCode price:priceInCountry(amount:$amount,from:"GBP")}}'

Note how the :ip and :amount entries in the uri parameter start with a colon. This means that the values in these segments will be passed into the query as named parameters. We can now make a normal GET request and watch as Kong makes a GraphQL request on our behalf:

curl localhost:8000/price/gbp/8.8.8.8/100

{
  "data": {
    "converted": {
      "countryCode": "US",
      "price": 115.69
    }
  }
}

Unwrap the response

Our customers can now interact with our upstream GraphQL endpoint as though it was a REST API, but there’s just one thing about the response that’s bothering me. GraphQL responses always contain a data key, but in our example, there’s only a single key inside the data. 

Let’s use Kong’s jq plugin to perform some post-processing on the response before responding to our caller. The jq plugin allows you to transform JSON data on request and response objects. We’ll use a short jq operation to return everything inside the .data key:

curl localhost:8001/services/graphql-demo/plugins -d name=jq -d config.response_jq_program='.data' -d config.request_if_media_type='application/json; charset=utf-8'

Once this plugin is installed, you can make a request to /price/gbp once again and see the transformed response:

curl localhost:8000/price/gbp/8.8.8.8/100

{"converted":{"countryCode":"US","price":115.69}}

Conclusion

We set out to compose an API from multiple different sources, then expose that as a simple REST endpoint for consumers. Using a combination of StepZen’s materializers and Kong Gateway plugins, we’ve managed to deliver exactly what our consumers need without adding a single line of code to our application.

If you’re interested in building something similar, you can get started with StepZen and learn more about Kong Gateway. If you’ve got any questions, I’d love to hear them on Twitter – you can find me at @mheap

Share Post

Tags: