ColdFusion Summit Notes: GET cfml - A Guide to Writing API Wrappers, Matthew Clemente
October 07, 2019
Everything has an API these days.
- File sharing
- Ron Swanson
- Art and design
- Image recognition
- Machine learning
- Weather
- AI
- Payments
- SMS / Voice
- and on and on and on
- List of tons of public APIs
Often as CF developers we need to write our own wrappers to use these tools
What are we talking about with “API wrapper”?
- ALI Library
- API Client
- Client Library
- REST SDK
- They’re all the same thing
- Component that maps 1 to 1 with the endpoints of an API
- So you can wrap the functionality of the API in your CFC
Don’t reinvent the wheel
How do i get started?
Step 1: Don’t write anything!
Maybe an API already exists
forgebox.io
github.com
CFML Slack
ColdFusion Portal
Search Engines
Bottom line: don’t write a wrapper if you don’t need to. use the resources the CF community already has.
Matt has a lot of wrappers he’s already written
Mailgun.cfc
Sendgrid.cfc
Every API is different
But when you’re writing wrappers, every API is the same
Because of REST
99% of APIs are REST-ful or REST-ish
Makes it easy to write a wrapper because it always follows the same pattern
REST is CRUD over HTTP.
Book: REST Assured: A Pragmatic Approach to API Design - Adam Tuttle
Basics of HTTP requests
- 6 elements
- Method
- Host
- path
- Params
- Headers
- Body
Only 5 methods you need to deal with:
GET, POST, PUT, PATCH, DELETE
When writing wrappers, every request goes to the same resource
And you’ll have different paths you add on for the different functions
Headers
Where content type and authentication takes place
Maybe you pass in basic auth here
Response
Even simpler
We get status code and status text
200 = everything’s great!
500 = server error
etc
Might also include rate limiting info in the response
Manual approach:
lots of code
API wrapper:
Much less code
Simpler way to get to the data
Don’t reinvent the wheel
Hyper
By Eric Peterson
CFML HTTP Builder
Fluent syntax to easily build HTTP requests
Build to solve SDK related frustration
Not ColdBox specific
box install hyper
API Wrapper Template
- CommandBox Tool for scaffolding CFML API Clients
- Fills in basic info about your API wrapper to get you started more quickly
- The wrapper is available on GitHub
box install api-wrapper-template
Getting Started With An API
- Take 5 minutes and read its documentation
- Will save you tons of time
- Public API providers put a lot of work into their documentation
- An API doesn’t exist unless people are using it
- What authentication takes place?
- Are there any testing endpoints?
- Limitations, formats, etc?
- Does it return JSON or XML?
Review other client libraries
The official clients (in PHP, Java, etc) will give you a lot of insight into how the API works. If you run into issues writing the CF wrapper, can probably glean insight from how the other wrappers do things.
The Cat API Wrapper
apiWrapper scaffold —-wizard
- apiName - name of the API this lib will wrap (i.e. Stripe)
- apiEndpointURL - based endpoint for API calls (api.stripe.com/v1)
- apiAuthentication - type of auth used (none, basic, apikey, etc)
- apiDocURL - URL of the API docs homepage
- name - name of the wrapper (stripecfc, etc)
- description - plain english description of what it does
- package = if it’s going on Forgebox or not (true/false), if we’re making a box.json file or not.
Building Your Requests
All the functions delegate the work to 1 “apiCall()” method
public struct function search( struct data = {} ) { return apiCall( “get”, “/images/search”, data ); }
Naming your methods
- Make sure there’s a method to the madness
- Model on API documentation
- Look to official clients
- CRUD pattern, generally a good guideline
- Be consistent
- If you’re working with a public API that has a client, just copy whatever their method names are into your wrapper so it’s easy to use.
- CRUD
- Create - POST verb - createItem() method
- Read - GET - getItem( id ) or listItems()
- Update - PUT/PATCH - UPDAteItem( id )
- Delete - DELETE - deleteItem( id )
Be consistent
Consistent pattern and naming makes it far easier to use
Less metal work if everything is consistent
HTTP Responses are more than just data
- Headers
- Status code
- Status text
- Return ALL that info to your users, the developer using the wrapper
- Info maybe not terribly important, but if it comes back w/ a status code “error”, you can handle that gracefully, etc.
- Don’t return just the file content, give users the full HTTP response so they can make decisions based on that.
Make Debugging Easy
Being able to analyze your HTTP requests is helpful when debugging
Have a way to change the base URL to make debugging things easier
Beeceptor
You can post to those services, they’ll show you all the data from the HTTP request
Looking at the data, it’s like you're seeing the calls from the API providers perspective
“includeRaw=true” option in API wrapper
includes the raw request from the HTTP call to the API
HTTP method, params, payload, and response
public any function init( … includeRaw = true )
Authentication
Some APIs have none but that number is rapidly dropping
- No auth - you don’t need to do anything
- Basic - username / password - base64 encoded in the “authorization” header
- API Key auth - unique identifier passed via headers (less frequently done via query string params)
- cfHttpParam( type=“header”, name=“api-key” value=“my key” )
- Open Auth - (OAuth) (Matt Gifford’s wrapper is good for this
- OAUTH - 3 parties involved: you, api provider, and the user
- OAuth manages auth between the 3
- Usually “a token is granted” and the token is passed in the header of the request
Environment Variables
- Keep your credentials out, don’t want them hard-coded into the application
- Best practice - separate the config from the code
- Enable users to not have to hard-code credentials
- Write wrappers so they can accept environment variables
- Straight forward in CF
system = createObject(“java”, “java.lang.system” ); val1 = system.getEnv( “key” ); val2 = system.getProper(“key” ); env = system.getEnv(); props = system.getProperties();
We can look in environment variables for credentials rather than force users to hard code them and pass them in manually
var foo = { applicationid = “alien_applicat_id”, app_key=“app_key” );
Aylien Text Analysis API
Natural language processing
Step 1 - read the docs
We see they want 2 headers passed for authentication
Handling API Keys for Auth -
Assumes in the init() you’ll want an apiKey, usually, but read the API docs to confirm
There is an official RFC spec for User Agents
RFC 7231 5.5.3 User-Agent
best practice in there says to always include a “user-agent” in our API wrapper’s calls.
Response Headers
- Can include important info about your rate limits, etc.
- X-RateLimit-Limit daily limit of your current plan
- X-RateLimit-Remaining - the amount remaining on your daily quota
- X-RateLimit-WhenReset - when the quotas are reset
- etc
Should we validate data in our wrapper?
No
You’re writing a wrapper for the API itself. Let the API do the validation
Don’t reinvent the wheel
Take data, pass to the API, and pass back the response
No reason for us to validate if the API already does it
In vast majority of cases, just take info from developer, and whatever the API sends back, show that to the developer
Your Users = Developers
Make their lives easier by identifying pain points.
Add “convenience methods” to make users lives easier
entitiesFromText() entitiesFromURL()
Could both delegate to entities() where the actual work is done
but then developers don’t have to deal with syntax quirks in entities() since it does several things.
Defusing arguments
- Pass all parameters as a struct
- Fast and easy to implement
- Preferable to listing args
- Puts burden on the developer
- Data not reusable
createAddress( required struct data ) { return apiCall( ‘POST’, ‘/address’, {}, arguments.data ); }
Most just say “you’re the developer put the data together in the right way and send it to our API”
Better - create a builder/helper component that builds the JSON that’s going to be sent to the API
More initial work
Better dev experience
/helpers/address.cfc
12 properties for the 12 things the API is expecting
within the CFC, every method returns the full component: via “return this”
Enables us to chain methods together.
Also enables us to write more fluent functions:
lob = new lobcfc.lob( testmode = true ); address = new lobcfc.helpers.address().city( “vegas” ).state( “NV” ).zip( “23455” ); result = lob.createAddress( address );
Final notes
- If writing API wrappers, document them
- If not for others, at least for yourself
- Your future self will thank you.
Don’t you prefer well documented repos? Docs shouldn’t be an afterthought. Easier to do them if you add them while you’re writing the code. Write a new method, write the docs for it right then and there.
Share your API wrappers!
Put them on github, forgebox, wherever others can take advantage of the hard work you’ve done.
Matt’s blog post “Publishing My First Package to ForgeBox”
GitHub: mjclemente
Twitter: mjclemente84
blog.mattclemente.com
Slide deck