Download - REST API's: Easier Than You Imagined
REST API’s:Easier Than You Imagined
@AdamTuttleFusionGrokker.com
You are here
Agenda
• REST 101
• Creating REST APIs with ColdFusion*
• Adding an API to an existing app
• Best Practices, Patterns, Anti-patterns
Agenda
• REST 101
• Creating REST APIs with ColdFusion*
• Adding an API to an existing app
• Best Practices, Patterns, Anti-patterns
*Using Taffy
I’m @AdamTuttle
That’s Me!
I make stuff
• Created May 2010
• #6 most watched ColdFusion project on GitHub
• 8+ people contributing
• Syntax doesn’t suck
I made Taffy
Who Uses Taffy?
REST 101
What is REST?
What is REST?
Request ResponseGET http://...HeadersRequest data
200 OKHeadersResponse data
<form action=”http://...” method=”POST”>
</form>
VocabularyVerb
Noun
Data Format (“Mime Type”)
Data
HTTP VerbsGET Read
POST Insert
PUT Update
DELETE Delete
OPTIONS Allowed Verbs
HEAD Headers Only
HTTP VerbsGET Read
POST Insert
PUT Update
DELETE Delete
OPTIONS Allowed Verbs
HEAD Headers Only
Safe
Unsafe
Idempotent
HTTP Nouns
•google.com/
•goodreads.com/books/Earnest-Cline/Ready-Player-One
•cfobjective.com/sessions/rest-apis-easier-than-you-imagined/
<form action=”http://...” method=”POST”>
</form>
<form action=”http://...” method=”POST”>
</form>
Noun
<form action=”http://...” method=”POST”>
</form>
NounVerb
<form action=”http://...” method=”POST”>
</form>
NounVerb
<input type=”hidden” name=”foo” value=”bar” />
Request Data
Data FormatHTML: text/html
Image: image/jpeg
JSON: application/json
XML: application/xml
ZIP: application/octet-stream
HTTP Status Codes
• 2xx = Success
• 3xx = Redirect
• 4xx = Client Error
• 5xx = Server Error
Do Not Write This Down
This will not be on the test
Raw Request$ telnet www.google.com 80Trying 173.194.73.104...Connected to www.google.com.Escape character is '^]'.GET /index.html HTTP/1.1Host: www.google.com{blank line}
Raw ResponseHTTP/1.1 200 OKDate: Sat, 11 May 2013 18:29:45 GMT[...]X-XSS-Protection: 1; mode=blockX-Frame-Options: SAMEORIGINTransfer-Encoding: chunked
<!doctype html><html..........
}ResponseHeaders
Web APIs work similarly
Raw RequestPOST /api/v1/tweets HTTP/1.1Host: twitter.comContent-Type: application/jsonAccept: application/json, */*;q=0.9User-Agent: MyTwitterApp{
“username”: “AdamTuttle”,“tweet”: “Hello, #CFObjective!”
}[blank line]
Raw ResponseHTTP/1.1 201 Tweet CreatedContent-Type: application/json; charset=UTF-8{
...}
<form action=”http://.../foo/bar” method=”POST”>
</form>
<input type=”hidden” name=”foo” value=”bar” />
POST /....../foo/barHost: www.example.comContent-Type: x-www-form-urlencodedAccept: application/json
foo=bar&flap=jacks
POST /....../foo/barHost: www.example.comContent-Type: x-www-form-urlencodedAccept: application/json
foo=bar&flap=jacks
No question mark
Creating REST APIs with ColdFusion*
*Using Taffy
Choices• Taffy
• Mach-II
• ColdBox
• Relaxation
• Powernap
• RestfulCF
• CF10
Why I (still) <3 Taffy• Simplest, Most-concise syntax
• Easily extended (security layer, etc)
• Doesn’t require access to CFAdmin
• Portable (Railo, CF8+ supported)
• Plays fine with any app framework
• ONLY Convention-over-Config option (CF10?)
• Open Source
Your FirstTaffy-powered API
Application.cfc
component extends=”taffy.core.api” {}
index.cfm
<!--- this space intentionally blank--->
An API w/ Results... that fits into a
Tweet
Application.cfccomponent extends=”taffy.core.api”{}
index.cfm
resources/hi.cfccomponent extends = ”taffy.core.resource” taffy_uri = ”/hi”{ function get(){ return representationOf(“hi”); }}
Application.cfccomponent extends=”taffy.core.api”{}
index.cfm
resources/hi.cfccomponent extends = ”taffy.core.resource” taffy_uri = ”/hi”{ function get(){ return representationOf(“hi”); }}
36
104
Anatomy of an API
Anatomy of an API
Application.cfcindex.cfm/resources/...
Application.cfccomponent extends=”taffy.core.api” {
variables.framework = {};
function applicationStartEvent(){} function requestStartEvent(){} function onTaffyRequest(){}
}
index.cfm
Resources
• Live in /resources subfolder• Each resource defines its own URI• Only implemented verbs are allowed• Collections vs. Members
Resources
component extends=”taffy.core.resource”taffy_uri=”/foo/{fooId}” {
public function get(fooId){} public function delete(fooId){}
}
3 Data Input Options
Tokens & Query Params
• Mapped by name to method args
• Tokens are the same for every method in a resource CFC and always required (otherwise 404)
• Query Params are optional; also passed by name
Simple Inputcomponent extends=”taffy.core.resource” taffy_uri=”/foo/{fooId}” {
function get(fooId, optional string city){ //... }}
<cfargument name=”city” required=”false” />
/api/index.cfm/foo/17?city=Philadelphia
Simple Inputcomponent extends=”taffy.core.resource” taffy_uri=”/foo/{fooId}” {
function get(fooId, optional string city){ //... }}
<cfargument name=”city” required=”false” />
/api/index.cfm/foo/17?city=Philadelphia
Simple Inputcomponent extends=”taffy.core.resource” taffy_uri=”/foo/{fooId}” {
function get(fooId, optional string city){ //... }}
<cfargument name=”city” required=”false” />
/api/index.cfm/foo/17?city=Philadelphia
Simple Inputcomponent extends=”taffy.core.resource” taffy_uri=”/foo/{fooId}” {
function get(fooId, optional string city){ //... }}
<cfargument name=”city” required=”false” />
/api/index.cfm/foo/17?city=Philadelphia
Simple Inputcomponent extends=”taffy.core.resource” taffy_uri=”/foo/{fooId}” {
function get(fooId, optional string city){ //... }}
<cfargument name=”city” required=”false” />
/api/index.cfm/foo/17?city=Philadelphia
Request Body InputTaffy Supports JSON and Form-encoded bodies as long as you set the Content-Type header
{“foo”:”bar baz”,”baz”:true}
foo=bar%20baz&baz=true
Data Output
Returning DatarepresentationOf method abstracts serialization
component extends=”taffy.core.resource”taffy_uri=”/foo” {
public function get(){ var querySvc = new Query(); //... return representationOf( someQuery ); }
}
Returning DatarepresentationOf method abstracts serialization
component extends=”taffy.core.resource”taffy_uri=”/foo” {
public function get(){ var querySvc = new Query(); //... return representationOf( myStruct ); }
}
Returning DatarepresentationOf method abstracts serialization
component extends=”taffy.core.resource”taffy_uri=”/foo” {
public function get(){ var querySvc = new Query(); //... return representationOf( myArray ); }
}
ColdFusion serializes queries ... abnormally.
{"COLUMNS":["COL1","COL2","COL3"],"DATA":[[3,3,true]]}
Taffy’s queryToArray() helper convertsqueries to an array of structures
[ {"CoL1":3,"col2":3,"col3":true}, {"CoL1":4,"col2":4,"col3":false}]
queryToArray()
queryToArray()
return representationOf(queryToArray( myQuery )
);
Response Headersreturn representationOf( ...).withStatus( 201, “Created”).withHeaders( { “X-MY-HEADER” = “My header value” });
Empty Success
return noData().withStatus(201, “Created”);
return noData();
More Advancedgithub.com/atuttle/Taffy/wiki
Dependency Injection/resources/FooService.cfc
/resources/FooCollection.cfc
component { function doStuff(){...}; }
component extends=”taffy.core.resource” ...{ property name=”FooService”;
function get(){ local.foo = this.FooService.doStuff(); //... }}
Resource Subfolders/resources/fooCollection.cfc “fooCollection”
/resources/Cat/Grumpy.cfc “CatGrumpy”
/resources/Services/Old/User.cfc “ServicesOldUser”
Bean Nam
e Generation
• Custom Token Regex
• Intercept requests w/ onTaffyRequest
• 3rd Party Bean Factories
• Verb:Method mapping w/ metadata
• Custom result serializers
• Environment specific config
• ETags for result caching
• ... much, much, much more
Adding an API to an existing App
Shared State
• Subfolder supported, not required
• Enable by using the same Application Name
• Shared variables*
*with great power comes great responsibility
Best Practices
Separate Collections &
Members
Version yourAPI from day 1.
/api/v1/foo/bar
Thoughtful URIs
Human-Readable URIs
Token vsQuery String
PUT or POST?
HATEOAS
HTTP Status Codes are your friend
Antipatterns
I hate you.
If you code like this...
GET request writes data
2+ requests / task
200 Error
Using Cookies
Getting SupportMailing List: bit.ly/taffy-users
IRC: #coldfusion on freenode and dalnetTweet me: @AdamTuttle
Thank You