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!

Made with Slides.com