API Builder

An API-First stock watchlist example using API Builder – Part 2: API build

An “API First” Stock Watchlist Example using API Builder

This is an example of using API Builder to create an API using an API-First approach. The API we’ll create is a stock watchlist, which displays a list of stocks that you would like to “watch” along with the latest price and the change from the market open price as illustrated below:

Watch List Example

In part 1 we designed our API using Stoplight and exported the OpenAPI 3.0 specification. Refer to that post for more details about the API specifications, but the API is called as follows:

GET /watchlist?stocklist=aapl,txn,amzn

with the following sample response:

[
    {
        "symbol": "AAPL",
        "lastPrice": 159.835,
        "change": 0.24501038
    },
    {
        "symbol": "TXN",
        "lastPrice": 177.34,
        "change": 1.0099945
    },
    {
        "symbol": "AMZN",
        "lastPrice": 3137.69,
        "change": 75.60986
    }
]   

In this blog post we’ll leverage the no-code/low-code features of API Builder to actually build the API. The final API Builder project can be downloaded here.

Build the API in API Builder

Initial setup

In order to have better ‘API First’ OpenAPI 3.0 design, development, and validation support in API Builder, I installed the API Builder OpenAPI flow-trigger plugin using:

npm install @axway/api-builder-plugin-ft-oas

Note that this will soon not be necessary as this feature will be available out of the box.

I modified the API Builder project to use API Key authentication that is set as an environment variable, API_KEY, (passed to the API as a header apikey). I did this by modifying the /conf/default.js file as follows:

module.exports = {
.
  .
  .
// API key
apikey: process.env.API_KEY,
  .
  .
  .
  accessControl: {
    apiPrefixSecurity: 'apikey', // none | basic | apikey | ldap | plugin
    public: []
  },
  .
  .
  .

I placed the API Key value in the API Builder /conf/.env file.

My API Builder API implementation uses the Yahoo Finance quote API. I used the free plan with 100 free quotes per day. The quote API is called as follows:

curl 'https://yfapi.net/v6/finance/quote?region=US&lang=en&symbols=AAPL' --header 'accept: application/json' --header 'X-API-KEY: WA5.........ZLep'

And it has the following response:

{
    "quoteResponse": {
        "result": [
            {
                "language": "en-US",
                "region": "US",
                "quoteType": "EQUITY",
                "typeDisp": "Equity",
                "quoteSourceName": "Nasdaq Real Time Price",
                "triggerable": true,
                "customPriceAlertConfidence": "HIGH",
                "pageViewGrowthWeekly": -0.05539666,
                "averageAnalystRating": "1.8 - Buy",
                "currency": "USD",
                "fiftyDayAverageChangePercent": 0.01417801,
                "twoHundredDayAverage": 154.6417,
                "twoHundredDayAverageChange": 14.173309,
                "twoHundredDayAverageChangePercent": 0.09165257,
                "marketCap": 2754959638528,
                "forwardPE": 25.733995,
                "priceToBook": 38.349613,
                "sourceInterval": 15,
                "exchangeDataDelayedBy": 0,
                "fiftyTwoWeekLowChange": 49.955,
                "fiftyTwoWeekLowChangePercent": 0.4202844,
                "fiftyTwoWeekRange": "118.86 - 182.94",
                "fiftyTwoWeekHighChange": -14.125,
                "fiftyTwoWeekHighChangePercent": -0.077211104,
                "fiftyTwoWeekLow": 118.86,
                "fiftyTwoWeekHigh": 182.94,
                "dividendDate": 1644451200,
                "priceEpsCurrentYear": 27.405033,
                "sharesOutstanding": 16319399936,
                "bookValue": 4.402,
                "fiftyDayAverage": 166.455,
                "fiftyDayAverageChange": 2.3600006,
                "earningsTimestamp": 1643301000,
                "earningsTimestampStart": 1651003200,
                "earningsTimestampEnd": 1651521600,
                "trailingAnnualDividendRate": 0.865,
                "trailingPE": 28.06567,
                "trailingAnnualDividendYield": 0.0052303784,
                "epsTrailingTwelveMonths": 6.015,
                "epsForward": 6.56,
                "epsCurrentYear": 6.16,
                "firstTradeDateMilliseconds": 345479400000,
                "regularMarketChange": 3.4349976,
                "regularMarketChangePercent": 2.0770333,
                "regularMarketTime": 1647977437,
                "regularMarketPrice": 168.815,
                "regularMarketDayHigh": 169.42,
                "regularMarketDayRange": "164.91 - 169.42",
                "regularMarketDayLow": 164.91,
                "regularMarketVolume": 61709634,
                "priceHint": 2,
                "tradeable": false,
                "regularMarketPreviousClose": 165.38,
                "bid": 168.99,
                "ask": 169.01,
                "bidSize": 11,
                "askSize": 13,
                "fullExchangeName": "NasdaqGS",
                "financialCurrency": "USD",
                "regularMarketOpen": 165.51,
                "averageDailyVolume3Month": 93131333,
                "averageDailyVolume10Day": 102346690,
                "exchange": "NMS",
                "shortName": "Apple Inc.",
                "longName": "Apple Inc.",
                "messageBoardId": "finmb_24937",
                "exchangeTimezoneName": "America/New_York",
                "exchangeTimezoneShortName": "EDT",
                "gmtOffSetMilliseconds": -14400000,
                "market": "us_market",
                "esgPopulated": false,
                "marketState": "REGULAR",
                "displayName": "Apple",
                "symbol": "AAPL"
            }
        ],
        "error": null
    }
}

The Yahoo Finance API Key is set as an environment variable called YAHOO_FINANCE_APIKEY. I also placed this in the API Builder /conf/.env file.

For reference, my /conf/.env file is below:

PORT=8080
LOG_LEVEL=debug
YAHOO_FINANCE_APIKEY=WA5....y7yzrZLep
API_KEY=Bz.....Xa

API implementation

After this initial project setup, I imported the watchlist OpenAPI 3.0 specification that was exported from Stoplight into API Builder:

Import OpenAPI Spec – Part 1
Import OpenAPI Spec – Part 2
Import OpenAPI Spec – Part 3

I created the following API flow by clicking on the Create Flow button on the GET /watchlist API after importing my spec.

get-watchlist flow

This main flow basically passes the array of stock symbols (the stocklist query parameter) to another flow, Get Stock Quote, using the invoke flow plugin so that we can loop over the array of stock symbols and retrieve stock quotes for each symbol.

The Get Stock Quote flow retrieves stock quotes for each stock symbol and transforms and reduces the payload.

The main flow then cleans up the responses and sets the final HTTP response.

Here are some highlights of the main flow:

  • The getStockQuotes flow node is an instance of the Invoke Flow flow node.
getStockQuotes Flow Node

You can see that it uses the for each method to call the flow for each element of an array.

The flow that is called is referenced by the Flow Listener ID, getquote. We’ll see this defined later when we review the Get Stock Quote below.

The array passed into the Items parameter is the stocklist query parameter, referenced using the $.request.query.stocklist selector.

  • The filerResponse flow node is an instance of the JavaScript flow node. It receives an array of responses from the getStockQuotes flow node and removes null entries:
async function code(data, logger) {
  let reply = [];
  data.response.forEach(function(item){
    if(item){
      reply.push(item);
    }
  });

  return(reply);
}

This response is passed back as the API response using the HTTP flow node.

Success HTTP Response
  • The other HTTP flow node handles errors and responds with a status code of 400.
Error HTTP Response

Get Stock Quote flow

The Get Stock Quote flow is shown below:

Get Stock Quote Flow

It was created manually in the Flows tab.

  • The first flow node is a Flow Listener Flow-Trigger with a Flow Listener ID of getquote. This was used in the main flow in order to invoke this flow.
Flow Listener Flow-Trigger
  • The prepareGetStockQuote JavaScript flow node prepares the Yahoo Finance quote API by constructing the url and header as follows:
async function code(data, logger) {
  let url='https://yfapi.net/v6/finance/quote?region=US&lang=en&symbols=';
  url+=data.request;

  let headers = {
    "X-API-KEY": process.env.YAHOO_FINANCE_APIKEY
  };

  return {url, headers};
}

It also defines two outputs:
$.prepareGetStockQuote.result
$.prepareGetStockQuote.error

  • The GetQuote REST Connector flow node makes the call to the Yahoo Finance quote API.
GetQuote REST Connector Flow Node

All of its outputs are set to $.response except for the 200 output which is set to $.GetQuote.response

  • The Exists Condition flow node checks for the existence of $.GetQuote.response.body.quoteResponse.result[0] to make sure that there is at least one response for the stock quote lookup.
Exists Condition Flow Node

Both of its outputs are set to $.response

  • The modifyResponse JavaScript flow node transforms and reduces the response, $.GetQuote.response.body.quoteResponse.result[0], by creating an object and modifying the field names from what is returned by Yahoo using the following:
async function code(data, logger) {
  let response = {};

  response.symbol = data.symbol;
  response.lastPrice = data.regularMarketPrice;
  response.change = data.regularMarketChange;

  return response;
}
  • The success flow response is set using the Set Response Flow Listener Response flow node.
Success Set Response Flow Listener Response Flow Node
  • The error flow response is set using the Set Response Flow Listener Response flow node with the Is Error toggle switch turned on.
Error Set Response Flow Listener Response Flow Node

The final API is called as follows:

curl 'http://localhost:8080/api/watchlist?stocklist=aapl,txn,amzn' --header 'apikey: BzX........LXa'

with the following reply:

[
    {
        "symbol": "AAPL",
        "lastPrice": 158.545,
        "change": -1.0449982
    },
    {
        "symbol": "TXN",
        "lastPrice": 177.54,
        "change": 1.2099915
    },
    {
        "symbol": "AMZN",
        "lastPrice": 3090.83,
        "change": 28.75
    }
]

Note that the /api prefix is automatically inserted by API Builder.

As mentioned above, the final API Builder project can be downloaded here.

A docker image of the API can be pulled from Docker Hub here.

Summary

In this blog post, we saw an example of building a stock Watchlist API in API Builder using the ‘API-First‘ approach. The API implements data aggregation, orchestration, payload reduction and field transformation and is based on an OpenAPI 3.0 specification from Stoplight that we designed in part 1.

Need help with API Builder? Extend your skills with Axway University.