REST API's: Easier Than You Imagined

Preview:

DESCRIPTION

Taffy has changed a fair amount, and for the better, since its release 2.5 years ago. It's also now used in production on Adobe ColdFusion and Railo every day of the year. It's baked into Slatwall, the open source eCommerce plugin for Mura. It's used by an augmented reality iPhone application to send data and photos between phones and servers. And still, it's the easiest, fastest way to get the job done from a CFML platform; and I'd like to help you get started writing API's today. This session will teach you the basics of REST, as well as everything you need to know to get started writing REST API's with Taffy. From "what's an HTTP verb?" to Dependency Injection, to Uploading Files, and everywhere in between. We'll also cover adding an API to an existing application because as we all know, starting from scratch is not often a privilege of which we get the pleasure.

Citation preview

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

<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