GET /cfml
A Guide to Writing API Wrappers
Matthew Clemente
APIs are awesome!
Superpowered Magic Legos
Everything has an API
Everything has an API
- Payments
- Search
- AI
- Physical Mail
- Maps/Geocoding
- APIs
- E-Commerce
- IoT
- SMS/Voice
- Accounting
- NLP/Text Analysis
- Books
- Calendars
- Cloud Storage
- Validation
- Dictionaries
- Weather
- Ticketing
- Music
- CI/CD
- Finance
- Beer
- Star Wars
- Law/Government
- Games and Comics
- Cryptocurrency
- Animals
- Machine Learning
- News
- Image Recognition
- Infrastructure
- File Sharing
- Ron Swanson
- Art and Design
What are we talking about?
Don’t Reinvent the Wheel
How do I get started?
🤔
Step #1
Don't Write Anything!
How I Got Here
Every API
is different
All APIs are the same
REST
REpresentational State Transfer
HTTP
HyperText Transfer Protocol
REST is CRUD over HTTP. Some exceptions are allowed, as long as they don’t become the rule.
Adam Tuttle, REST Assured: A Pragmatic Approach to API Design, 2019
The Cat API
Manual Approach
API Wrapper Approach
Don’t Reinvent the Wheel
hyper
A CFML HTTP Builder
box install hyper
- Fluent syntax to easily build requests
- Built to solve SDK-related frustration
- Not ColdBox specific
- By module-wizard @elpete
api-wrapper-template
A CommandBox tool for scaffolding CFML API Clients
box install api-wrapper-template
Getting Started
- Read the documentation
- Authentication
- Testing Endpoints
- Limitations, Format, etc.
- Review other client libraries
The Cat API Wrapper
apiWrapper scaffold --wizard
apiName* | Name of the API this library will wrap, i.e. Stripe |
apiEndpointUrl* | Base endpoint URL for API calls, i.e. https://api.stripe.com/v1 |
apiAuthentication | Type of authentication used [None, Basic, Apikey, Other] |
apiDocUrl | URL of the API documentation homepage |
name | Name for the wrapper [i.e. StripeCFC] |
description | A short description of the wrapper. |
author | Name of the author of the wrapper. |
package | Create a box.json - for use as a Forgebox package (yes/no) |
The Cat API Wrapper
apiwrapper scaffold
apiName = "The Cat API"
apiEndpointUrl = "https://api.thecatapi.com/v1"
apiAuthentication = "apikey"
apiDocUrl = "https://docs.thecatapi.com/"
name = "CFCat"
description = "Some people are dog people. Others are
cat people. This wrapper will provide
the latter with cats on demand."
package = "true"
Directory Structure
Building Your Requests
Making the Request
Naming Your Methods
- Model on API documentation
- Look to official clients
- CRUD pattern
- Be Consistent
Make sure there's a method to your madness
CRUD | HTTP Verb | Method |
---|---|---|
Create | POST |
createItem(data) |
Read | GET |
getItem(id) listItems() |
Update | PUT/PATCH |
updateItem(id) |
Delete | DELETE |
deleteItem(id) |
Naming Conventions
Be Consistent
catapi = new cfcat.catapi();
catGif = catapi.getRandomGif().data;
writeOutput( '<img src="#catGif.url#">' );
HTTP Response > Data
Make Debugging Easy
RequestBin / Hookbin / Mockbin
Being able to analyze your HTTP requests is helpful when debugging.
catapi = new cfcat.catapi( includeRaw = true );
data = {
"size": "full",
"breed_id": "raga",
"limit": 1
};
writeDump( catapi.search( data ) );
Authentication
Authentication typically involves providing credentials via Headers
No Authentication | This is easy! You don't need to do anything! |
Basic Authentication | Base64 encoded username:password in Authorization header |
API Key Authentication | Unique identifiers passed via headers (less frequently, via query params or the body) |
Open Authorization (OAuth) | Access Token used, following authentication and permission |
Authentication
How do we handle it in our CFML API clients?
No Authentication | This is easy! You don't need to do anything! |
Basic Authentication | cfhttp username and password attributes |
API Key Authentication |
cfhttpparam( type="header", name="api-key", value=value ); |
Open Authorization (OAuth) | https://github.com/coldfumonkeh/oauth2 |
Environment Variables
Keeping credentials in environment variables
provides practical and security benefits
Environment Variables
// Get access to the system class.
system = createObject( "java", "java.lang.System" );
// Get a specific environment variable
value1 = system.getenv( 'key' );
// Get a specific system property
value2 = system.getProperty( 'key' )
// Get a struct of env vars or system props
environment = system.getenv();
properties = system.getProperties();
Environment Variables
Handled automatically in api-wrapper-template
Aylien Text Analysis API
Aylien Text Analysis API
Aylien Text Analysis API
apiwrapper scaffold
apiName = "Aylien"
apiEndpointUrl = "https://api.aylien.com/api/v1"
apiAuthentication = "apikey"
apiDocUrl = "http://docs.aylien.com/textapi/"
name = "ayliencfc"
description = "Access Aylien's Natural Language
Processing (NLP) API to extract meaning
and insight from textual content."
package = "true"
Handling API Keys
Handling API Keys
Making a Request
URL to Analyze
Aylien Entity Analysis
aylien = new aylien.aylien();
entities = aylien.entities( url = 'http://bit.ly/cfsummit2019' ).data;
writeDump( entities );
Aylien Response Headers
aylien = new aylien.aylien();
entities = aylien.entities( url = 'http://bit.ly/cfsummit2019' );
writeDump( entities );
Shouldn't We Validate?
No...
Don’t Reinvent the Wheel
Leave Validation to the API
aylien = new aylien.aylien();
entities = aylien.entities( url = 'I am not a URL!' );
writeDump( entities );
Your Users = Developers
Make their lives easier by identifying pain points.
entities = aylien.entities( 'http://bit.ly/cfsummit2019' ).data;
Developer-focused Refactor #1
Developer-focused Refactor #2
Lob API for Printables
Lob API for Printables
apiwrapper scaffold
apiName = "Lob"
apiEndpointUrl = "https://api.lob.com/v1"
apiAuthentication = "basic"
apiDocUrl = "https://lob.com/docs"
name = "lobcfc"
description = "Wrap the Lob API to verify
addresses and send physical
mail programmatically."
package = "true"
Lob API for Printables
Basic Auth
& Test Endpoints
Basic Auth
& Test Endpoints
Lob Test Endpoint
lob = new lob.lob( forceTestMode = true );
//etc etc
result = lob.createPostcard( postcard );
Getting Into Arguments
Losing Arguments
Defusing Arguments
- Fast and easy to implement
- Preferable to listing arguments
- Puts burden on developer
- Data not reusable
Pass all parameters as a struct
How To Win Arguments
Helper components with fluent interfaces
- More initial work
- Better developer experience
- Consistent data produced
Helper Properties
/helpers/address.cfc
Chaining Methods
/helpers/address.cfc
Something is Wrong
Document Your Wrapper
(Because don't you prefer well documented repositories)
- Docs ≠ afterthought
- Add a method => add it to the docs
- Link to official documentation
- Quick start guide/example
- Describe the install procedure
- List the methods you provide
- Model a repo you admire
🧐📖
Share Your API Wrappers!
Don’t Reinvent the Wheel
GET /cfml
A Guide to Writing API Wrappers
Matthew Clemente
GET /cfml - A Guide to Writing API Wrappers
By mjclemente
GET /cfml - A Guide to Writing API Wrappers
Presentation for Adobe ColdFusion Summit 2019
- 4,087