Building API Integrations
with Hyper

About Me

  • Michael Born
  • Central NY
  • Accidental carpenter

Topics

  • Making HTTP requests
  • Standardizing error handling
  • Reducing boilerplate
  • Using and securing API secrets
  • Structuring an API wrapper
  • Testing API integrations
  • Publishing to ForgeBox

So You Need an API wrapper?

  • REST (over HTTP)
  • Publicly accessible
  • API key auth
    • (not covering oAuth today)

Getting Started

  • Start with an account
  • Get API key (or client id/secret)
  • Create a module
    • or at least a folder!
  • Install Hyper dependency
  • Store in .env
  • Read environment variables into app

Create an Account

  • Doing "due diligence"
  • Where the rubber meets the road
  • What are the costs involved?
  • Use the "sandbox" if available
  • if oAuth
    • may require HTTPs
    • may not allow localhost development

and obtain an API key

Create a Module

box coldbox create module name=cfYNAB

Install Hyper as a Dependency

cd modules_app/cfYNAB
box install hyper

Store Secrets in .env

# .env

# YNAB API credentials
YNAB_URL=https://api.youneedabudget.com/v1/

# See https://api.youneedabudget.com/#personal-access-tokens
YNAB_ACCESS_TOKEN=XYZ_FIXME

STOP!

Add .env to .gitignore!

# .gitignore

# Ignore all SECRET stuff
.env

Read .env file

box install commandbox-dotenv

or

var envFile = expandPath( ".env" );
if ( fileExists( envFile ) ){
    var props = CreateObject( "java", "java.util.Properties" ).init();
    props.load( CreateObject( "java", "java.io.FileInputStream" ).init( envFile ) );
    
    var availableProps = props.propertyNames();
    
    while( availableProps.hasNext() ){
        var propName = availableProps.next();
        javaSystem.setProperty( propName,  props.getProperty( propName ) );
    }
}

store as environment variables

Configure API Settings

// ModuleConfig.cfc
function configure(){
    settings = {
        api_url: getSystemSetting( "YNAB_URL", "https://api.youneedabudget.com/v1/" ),
        
        // See https://api.youneedabudget.com/#personal-access-tokens
        personal_access_token: getSystemSetting( "YNAB_ACCESS_TOKEN" )
    };
}

via ModuleConfig.cfc

Configuring Hyper

Creating a reusable API client

// ModuleConfig.cfc
function configure(){
    // ...
    binder.map( "YNABClient" )
        .to( "hyper.models.HyperBuilder" )
        .asSingleton();
}
// ModuleConfig.cfc
function configure() {
    // ...
    binder.map( "YNABClient" )
        .to( "hyper.models.HyperBuilder" )
        .asSingleton()
        .initWith(
            baseUrl = settings.api_url,
            headers = {
                "Authorization" = "Bearer #settings.personal_access_token#"
            }
        );
}

Using the Hyper API Client

/**
 * I model all YNAB transaction requests
 */
component {
  property name="YNAB" inject="YNABClient";
  
  // ...
}

Make This My First Request

How to structure an API wrapper?

It depends on the API!

How to structure an API wrapper?

Prefer:

  • Model per resource
  • Method per verb

Avoid:

  • All endpoints in a single file
  • A single file per API call: new GetBudgets.call()

How to structure an API wrapper?

Creating an API Model

// models/Budgets.cfc
/**
 * I handle YNAB API requests for the /budgets endpoint.
 * 
 * @url https://api.youneedabudget.com/v1#/Budgets/getBudgets
 */
component {
  property name="YNAB" inject="YNABClient";
  
  /**
   * Constructor
   */
  public component function init(){
    return this;
  }
}

Create a model method

function getBudgets(){
    return YNABClient.get( "/budgets" );
}
function getBudgets( boolean include_accounts ){
    var queryParams = {};

    if ( !isNull( arguments.include_accounts ) ){
        queryParams[ "include_accounts" ] = arguments.include_accounts;
    }
    return YNABClient.get( "/budgets", queryParams );
}
function getBudgets(){
    return YNABClient.get( "/budgets", arguments );
}
/**
 * Retrieve YNAB budgets from YNAB API
 * 
 * @include_accounts true to return array of budget accounts with each budget entry
 * 
 * @see https://api.youneedabudget.com/v1#/Budgets/getBudgets
 */
function getBudgets( boolean include_accounts ){
    var queryParams = {};

    if ( !isNull( arguments.include_accounts ) ){
        queryParams[ "include_accounts" ] = arguments.include_accounts;
    }
    return YNABClient.get( "/budgets", queryParams );
}

Using the API Wrapper

Using the API Wrapper

// handlers/YNAB.cfc
/**
 * Manage YNAB connection
 */
component {

    property name="Budgets" inject="Budgets@cfYNAB";

    /**
     * Show YNAB budgets
     */
    function index( event, rc, prc ){
        prc.data = budgets.getBudgets();

        event.setView( "account/myBudget" );
    }
}

The HyperResponse Object

The HyperResponse makes it easy to

  • check result success/failure
  • get response as JSON
  • get response status code, data, etc.
  • get the request execution time
  • etc, etc.

Thank You!

Building API Integrations with Hyper

By Michael Born

Building API Integrations with Hyper

Learn how to connect to a public API using Hyper in this webinar by Michael Born, where we'll cover API integrations from storing secrets to writing unitBuilding API Integrations with Hyper tests to publishing the finished, reusable library on ForgeBox.

  • 509