Introduction to API development with Taffy, a developer-friendly and convention-based framework for ColdFusion and Lucee.
Code samples at: https://github.com/TheRealAgentK/taffydemos
2. AGENDA
▸ What is Taffy?
▸ REST in a nutshell
▸ But is it “RESTful ENOUGH”?
▸ Taffy setup and basic flow
▸ Advanced configuration
▸ API Blueprint and other tools
4. WHAT IS TAFFY?
TAFFY
▸ TLDR version: Taffy is a framework to build REST-based APIs in CFML
▸ Developed by Adam Tuttle
▸ Currently in version 3.1, incepted around 2011 (MIT license)
▸ Very stable, very powerful
▸ Allows the creation of REST-based APIs in CFML independent of any vendor
implementation - works with Lucee and ACF
▸ Can be integrated with other frameworks
5. WHAT IS TAFFY?
WHY TAFFY?
“But hey, both Lucee and Adobe CF have their own implementation of REST-
API handling and it’s SO EASY to use and it has SELF-DOCUMENTATION!
That’s great, ey?”
▸ At first glance it might seem like a great idea for getting started. But:
▸ Various issues (created URL structures, JSON handling, clunky syntax)
▸ Significantly lock-in to a vendor’s implementation
▸ Overall developer-unfriendly approaches
6. WHAT IS TAFFY?
BENEFITS OF TAFFY
▸ This is not a talk about the deficiencies of REST in ACF or Lucee
▸ This is why Taffy is great:
▸ Developer-friendly and easy to get started with
▸ Backwards-compatible to ACF 8
▸ Convention over configuration
▸ Hardly any boilerplate code
7. REST IN A NUTSHELL
https://www.flickr.com/photos/kikasz/2891113802/
8. REST IN A NUTSHELL
WHAT IS REST?
▸ REpresentational State Transfer - a method to communicate over a network
▸ Mostly used over HTTP, technically protocol-independent
▸ 2 phases:
▸ Request - noun, verb, MIME type, headers, data
▸ Response - status code, status text, headers, data
9. REST IN A NUTSHELL
THE REQUEST (I)
▸ Nouns - identifier of the data we want to work with, e.g. /users or /users/232
▸ Main verbs:
▸ POST: creating a new record (unsafe)
▸ PUT: updating existing record (idempotent)
▸ GET: retrieving existing record (safe)
▸ DELETE: deleting record (idempotent)
10. REST IN A NUTSHELL
THE REQUEST (II)
▸ Less commonly used verbs:
▸ HEAD: similar to GET, but only returns headers (safe)
▸ OPTIONS: used to check which verbs are available (safe)
▸ PATCH: similar to PUT, updating individual properties (idempotent)
▸ MIME type:
▸ Client and API server agree on the data types for exchange, e.g. text/html,
application/xml etc
11. REST IN A NUTSHELL
THE REQUEST (III)
▸ Headers - supply metadata with the request (Accept, Content-Type)
▸ Data - the data sent from the client to the server
▸ Can conceptually be of any format
▸ Most common: JSON
12. REST IN A NUTSHELL
THE RESPONSE
▸ Status code & Status text:
▸ “200 OK”
▸ “404 Not Found”
▸ “500 Server Error” …
▸ There are a lot more specific status codes, such as 201, 301, 302, 400, 401
etc.
▸ Headers and Data
13. REST IN A NUTSHELL
WHY DO PEOPLE LIKE AND USE REST?
▸ Works naturally with the HTTP protocol
▸ Other alternatives:
▸ SOAP-XML: very enterprise-y, bloated and complex
▸ XML-RPC: similar to REST, but ignores the power of the verb
15. BUT IS IT RESTFUL ENOUGH?
THE REALITY IS MORE COMPLEX
▸ Roy Fielding’s 2000 dissertation about what we today know as REST
▸ Followed up by a series of blog posts from ~2008 about HATEOAS
(Hypermedia As The Engine Of Application State)
▸ prescribing additional requirements and behaviours
▸ often considered as ideal, but impractical in a lot of ways
▸ Leads to a lot of discussion what REST is and how one has to use it
16. BUT IS IT RESTFUL ENOUGH?
MORE MATURE?
▸ Plain JSON
▸ HATEOAS
{
"name" : “iPhone 7"
}
{
"name": “iPhone 7",
"links": [ {
"rel": "self",
"href": "http://localhost:8080/product/1"
} ]
}
20. TAFFY SETUP AND BASIC FLOW
INSTALLATION
▸ Download .zip-file from Github or taffy.io
▸ Installation options:
▸ Global mapping /taffy
▸ /taffy physically in webroot
▸ Installation in sub-directory of site (needs application-specific mappings)
21. TAFFY SETUP AND BASIC FLOW
DRIVEN BY APPLICATION.CFC
▸ Extends taffy.core.api
▸ Needs to call onApplicationStart() and
onRequestStart() on the parent CFC
▸ Earlier versions of Taffy had their own
Application.cfc events - it was
recommended to actually not implement
onApplicationStart() etc
▸ That’s gone since Taffy 2.0
component extends="taffy.core.api"
{
this.name = "TaffyDemo";
function onApplicationStart()
{
application.productList = [
{ "id":1, "name":"iPhone 7" },
{ "id":2, "name":"iPhone 7+" },
{ "id":3, "name":"iPad Pro 9.7inch" }
];
return super.onApplicationStart();
}
function onRequestStart()
{
return super.onRequestStart();
}
}
22. TAFFY SETUP AND BASIC FLOW
CONVENTION OVER CONFIGURATION
▸ Taffy tries to NOT be in your way
▸ Examples:
▸ Default function names for resources are get(), post(), put(), delete()
▸ Looks for resources in a /resources directory
▸ Lots of things are just fine and work out of the box naturally because of
sensible conventions
24. TAFFY SETUP AND BASIC FLOW
RESOURCES (I)
▸ Extend taffy.core.resource
▸ Live in the /resources directory or a /resources mapping
▸ Can be changed - requires dropping the default bean factory
▸ Good practice:
▸ Have a CFC for a “thing” and for a “collection of things” each
▸ My convention: “SomeThing” and “SomeThingCollection”
▸ A resource should at least implement one verb function
25. TAFFY SETUP AND BASIC FLOW
RESOURCES (II)
▸ taffy_uri defines the public URL of your resource
▸ representationOf() triggers the default serialiser
▸ withStatus() sets the HTTP status of the response (defaults to “200 OK”)
component extends="taffy.core.resource" taffy_uri="/product"
{
public function get(){
return representationOf(application.productList).withStatus(200);
}
}
26. TAFFY SETUP AND BASIC FLOW
RESOURCES (III)
▸ Passing {productId} token into the resource’s URL
▸ Returning 404 if there’s no record found
component extends="taffy.core.resource" taffy_uri="/product/{productId}"
{
public function get(numeric productId){
for (var product in application.productList) {
if (product["id"] == arguments.productId) {
return representationOf(product).withStatus(200, "OK");
}
}
return noData().withStatus(404, "Not Found");
}
}
27. TAFFY SETUP AND BASIC FLOW
RESOURCES (IV)
▸ If a verb is not implemented in a resource, Taffy will return “405 Not Allowed”
▸ Tokens will automatically become part of the arguments scope
▸ Tokens support regular expressions: /product/{pId:d{4}} - /product/{pId:[A-Za-z]+}
▸ Query string parameters will ALSO automatically become part of the arguments scope
▸ Use .noData() if the intention is to not return data
▸ Similar to .withStatus(…), it’s possible to chain .withHeaders(…) to the return
statement
29. TAFFY SETUP AND BASIC FLOW
TAFFY DASHBOARD
▸ Dashboard allows to:
▸ execute requests (incl. Query Params, Headers and Basic Auth)
▸ inspect the response
▸ see a limited amount of documentation
▸ Should (usually) be switched off in production
▸ Default: caches API, needs explicit reloads
30. TAFFY SETUP AND BASIC FLOW
CHANGING DATA - WHERE TO PUT POST?
▸ If the POST method was in Product.cfc - we’d have an issue.
▸ The URL route requires us to use /product/{productId}
▸ POST therefore has to life in ProductCollection.cfc
▸ PUT however (updating existing data) can happily go into Product.cfc
▸ URL matching: /product/search will match before /product/{productId}
31. TAFFY SETUP AND BASIC FLOW
GOOD PRACTICES FOR PUT AND POST
▸ If you created a record with POST:
▸ return “201 Created” with the created data
▸ potentially return x-inserted-id in the header
▸ If you updated a record with PUT:
▸ return “200 OK” with the updated data
▸ if the record to be updated didn’t exist, create it (see above)
33. ADVANCED TASKS AND CONFIG
BREAKING AWAY FROM THE CONVENTIONS
▸ Metadata:
▸ Annotate functions to be triggered by a specific verb: taffy_verb=“<VERB>”
▸ Necessary for extended verbs like OPTIONS, HEAD, PATCH. Optional for
standard verbs.
▸ There’s more metadata in serialisers (later)
34. ADVANCED TASKS AND CONFIG
BREAKING AWAY FROM THE CONVENTIONS
▸ Configuration: variables.framework struct in Taffy’s Application.cfc
▸ reloadKey/reloadPassword vs. reloadOnEveryRequest
▸ serializer/deserializer (later)
▸ disableDashboard
▸ jsonp/allowCrossDomain
▸ beanFactory - allows hooking into Coldspring, DI/1 etc…
35. ADVANCED TASKS AND CONFIG
AUTHENTICATION AND SECURITY
▸ Firstly - what do you want to achieve?
▸ Requiring an API Token?
▸ Fine-grain security?
▸ oAuth
▸ HTTP Basic Auth
▸ SSL
36. ADVANCED TASKS AND CONFIG
API TOKEN (I)
▸ Best place to start is onTaffyRequest(…)
▸ Return true to continue processing the request
▸ Return a representation object to abort the request
▸ Two common options for passing token:
▸ URI parameters and/or as part of the data
▸ In the header
37. ADVANCED TASKS AND CONFIG
API TOKEN (II)
function onTaffyRequest(verb, cfc, requestArguments, mimeExt,
headers, methodMetadata, matchedURI) {
if (not structKeyExists(arguments.requestArguments, "apiKey") ||
!Len(arguments.requestArguments["apiKey"])) {
return noData().withStatus(401, "Unauthorized");
}
if (!arrayFind(application.validAPIKeys,
arguments.requestArguments["apiKey"])) {
return noData().withStatus(403, "Forbidden");
}
return true;
}
38. ADVANCED TASKS AND CONFIG
CUSTOM SERIALISERS (I)
▸ Default serialiser is JSON (CFML-server built-in)
▸ Extend from taffy.core.baseRepresentation
▸ Implement getAsX functions
▸ getAsXML, getAsJSON, getAsYML etc
39. ADVANCED TASKS AND CONFIG
CUSTOM SERIALISERS (II)
component extends="taffy.core.baseSerializer"
{
variables.anythingToXML = application.anythingToXML;
variables.jsonUtil = application.jsonUtil;
function getAsXML() output="false" taffy_mime="application/xml" taffy_default="false" {
return variables.anythingToXML.toXml(variables.data);
}
function getAsJSON() output="false" taffy_mime="application/json" taffy_default="true" {
return variables.jsonUtil.serialize(variables.data);
}
}
40. ADVANCED TASKS AND CONFIG
RATE LIMITS
▸ In a nutshell:
▸ manage access log (api key & time) in DB or key/value storage
▸ onTaffyRequest logs requests and check if limits are exceeded
▸ if not exceeded, process incoming request
▸ if exceeded, return status code “429 Too Many Requests”
41. ADVANCED TASKS AND CONFIG
API VERSIONING
▸ Multiple schools of thought:
▸ URI vs header
▸ Make sure you version from day 0!
▸ I tend to use /v1/product, /v2/product etc.
▸ Maps nicely on directory structures in /resources
▸ Update version when breaking compatibility
▸ Consider SemVer (Semantic Versioning) if your API is changing a lot
43. API BLUEPRINT
WHAT IS IT?
▸ Description and specification language for web APIs (http://apiblueprint.org)
▸ From Apiary.io - but can be used independently from that
▸ Language spec: https://github.com/apiaryio/api-blueprint
▸ Offers platform-neutral documentation and additional tooling
▸ Aglio
▸ API-Mock
44. API BLUEPRINT
DOCUMENTATION WITH AGLIO
▸ Very simple markup language
▸ npm install -g aglio (https://www.npmjs.com/package/aglio)
▸ aglio -i documentation.md -o documentation.html
▸ MD structure:
▸ # Group
▸ ## Resource
▸ ### Action
45. API BLUEPRINT
MOCK SERVER WITH API-MOCK
▸ Creates a mock server for your API (https://github.com/localmed/api-mock)
▸ npm install -g api-mock
▸ api-mock ./documentation.md
▸ Defaults to port 3000
46. API BLUEPRINT
PAW
▸ REST API test and management tool on OS X
▸ Recording/Playback/Testing etc
▸ Has API Blueprint support through extensions
48. FINAL THOUGHTS
WHAT DID WE COVER?
▸ Taffy - what is it?
▸ How to setup and the foundations
▸ Discussed some advanced topics in more detail
▸ High-level overview of API Blueprint
49. FINAL THOUGHTS
GET IN TOUCH
Kai Koenig
Email: kai@ventego-creative.co.nz
Twitter: @AgentK