-
Notifications
You must be signed in to change notification settings - Fork 117
Getting Started
This page gives a brief overview of implementing a REST API using Taffy. For an index of available documentation or for a high level explanation of Taffy, see the Home page. For in-depth documentation on all available methods, see the Index of API Methods.
Like videos more than reading? I presented about REST and Taffy, covering the basics of Taffy and a few more advanced examples at the 2011 cfObjective conference. Watch the recording and get my slides here.
For the most part, Taffy uses convention over configuration, but there are a few configuration details that can't be pragmatically solved with conventions. In those cases, configuration kept to a minimum using metadata wherever possible.
Taffy is tested and supported on Adobe ColdFusion 7, 8, and 9. Hopefully, Railo 3.3 support will be added soon.
Simply unzip the taffy folder into your web-root. An application-specific mapping to a non-web-accessible folder is not sufficient; however a global mapping in your ColdFusion Administrator will work -- in which case you should map /taffy
to the location of your unzipped taffy folder.
The code examples below use the ColdFusion 9 script component syntax for its terseness, but this is not a requirement. If you are more comfortable with <cfml tags>
then you are free to use them instead.
Much like a FW/1 application, Taffy implements a majority of its logic in Application.cfc, as a base class that the Application.cfc of your API will extend. At a minimum, your API's Application.cfc needs the following:
component
extends="taffy.core.api"
{
//the name can be anything you like
this.name = 'Your_API_App_Name_Here';
//use this instead of onApplicationStart
void function applicationStartEvent()
{
}
//use this instead of onRequestStart
void function requestStartEvent()
{
}
}
What's going on here?
-
Application.cfc extends
taffy.core.api
-- this is most of what makes Taffy work. -
It is strongly recommended that you DO NOT implement the
onApplicationStart
,onRequestStart
,onRequest
, oronError
methods; these are used by the framework. If you choose to do so, you should carefully consider the placement ofsuper.onApplicationStart()
/super.onRequestStart()
/super.onRequest()
in your overridden version. Alternatively, Taffy will callapplicationStartEvent
duringonApplicationStart
, andrequestStartEvent
duringonRequestStart
, if you need to add code to these event listeners. The same is true foronError
, except that there is no pre-determined method to override - you should overrideonError
and if necessary, callsuper.onError()
.
The Index of API Methods lists all methods available in Application.cfc, what they do, and where you can use them.
Each Resource in your API (eg. Person, Product) should be defined as its own CFC. In a Taffy API, you implement Collections and Members as separate CFCs (eg. personCollection.cfc
and personMember.cfc
, productCollection.cfc
and productMember.cfc
). Taffy won't expose them by name, so you can name them whatever you like -- I just happen to like naming them fooCollection vs fooMember to make it easy to determine which CFC handles individual records (member) and which handles sets of records (collection).
Here is an example resource implementation:
personCollection.cfc:
component
extends="taffy.core.resource"
taffy_uri="/people"
{
public function get(string eyeColor = ""){
//query the database for matches, making use of optional parameter "eyeColor" if provided
//then...
return representationOf(someCollectionObject).withStatus(200); //collection might be query, array, etc
}
}
This resource will respond for http://example.com/api/people
, as well as http://example.com/api/people?eyeColor=green
. When the query string parameters are provided, they will be passed to the function by name.
And here's a similar implementation of a member for the same data type (person):
personMember.cfc:
component
extends="taffy.core.resource"
taffy_uri="/people/{personName}"
{
public function get(string personName){
//find the requested person, by name
//then...
return representationOf(someMemberObject).withStatus(200); //member might be a structure, ORM entity, etc
}
}
This resource will respond for http://example.com/api/people/john-smith
. For a member GET, the identifying information --name, in this case-- is usually unique (enforced by your database indexes and app logic), so there is rarely a reason to include query string parameters as optional arguments. That said, it is supported.
-
Every Resource CFC -- both member and collection types -- extend
taffy.core.resource
. -
Every Resource CFC should implement at least 1 of 4 methods:
- get
- post
- put
-
delete
As you may have guessed, these map directly to the HTTP verb used by the API consumer in their request.
- If the consumer uses the POST verb, it runs the POST method in the corresponding CFC.
- Since the POST, PUT, and DELETE methods are not implemented in the example above, usage of each of the corresponding verbs against the example resource will be refused, with HTTP status code
405 Not Allowed
.
-
Tokens from the component metadata attribute
taffy:uri
(ortaffy_uri
), defined as{token_name}
(including the curly braces, see example above) will be extracted from the URI and passed by name to the requested method. In addition, all query string parameters (except those defined for framework specific things like debugging and reloading) will also be included in the argument collection sent to the method.
For example:GET /product/44?color=Blue
will result in the GET method being called on the Product collection, with the arguments:{ productId: 44, color: "Blue" }
.
TheproductId
parameter is defined by thetaffy:uri
attribute (set at the component level, not the function level), and thecolor
parameter is defined as an optional argument to the function, and was provided in the query string. It is not possible to put optional parameters in the URI -- they need to be in the query string. -
The
representationOf
method -- a special method provided by thetaffy.core.resource
parent class -- creates a new object instance capable of serializing your data into the appropriate format. A class capable of serializing as JSON using ColdFusion's native serialization functionality is included with the framework and used by default. To use a custom representation class on a per-request basis, pass the dot-notation CFC path (or bean id; more on this in Bean Factories) of your custom representation object as the (optional) 2nd argument to therepresentationOf
method. You can also override the global default representation class by using setDefaultRepresentationClass. See Using a Custom Representation Class for more details on that.- In some cases, you might not want to return any data and a status code is sufficient (for example, indicating a successful delete). In this case, use noData() instead of
representationOf()
.
- With either representationOf or noData, you may optionally use the withStatus method to set the HTTP status code, and/or the withHeaders method to add additional headers to the response. If you do not include
withStatus(...)
, a default status of 200 will be returned. You should familiarize yourself with Common API HTTP Status Codes.
- In some cases, you might not want to return any data and a status code is sufficient (for example, indicating a successful delete). In this case, use noData() instead of
Note: The namespacing of Taffy's metadata attributes, such as taffy_uri
is supported using two formats: underscores ("taffy_uri"), and colons ("taffy:uri"). The latter is my preferred style, but not supported in CF9.01 script component syntax (ColdFusion Bug #86749), which is why the former was added. However, if you're writing your components with tags, the colon-syntax is supported.
Now that you've got a working API, you and your consumers need to know how to access it. This is extremely simple.
The folder containing your API should contain, at a minimum, the Application.cfc from Step 1, and an empty index.cfm. (index.cfm's contents, if any, will be ignored, but it must exist to allow ColdFusion to handle the request.) To compose a complete URL, append the URI to the location of index.cfm.
Assuming your API is located at http://example.com/api/index.cfm
, and you've implemented the resource with URI /products
and the GET method, then you could open up the URL: http://example.com/api/index.cfm/products
in your browser and the data would be returned, serialized using the default mime type (JSON unless otherwise defined).
Some people would prefer to remove the /index.cfm
portion of the URL (I am one of them). To do so, you must use URL-rewriting. The good news is that you should only need one simple rule. With Apache you can use mod_rewrite, or with IIS 6 you can use IIRF (free) or ISAPI Rewrite (paid). IIS 7 has url rewriting built-in. For specific rewriting rule examples for each engine, see URL Rewrite Rule Examples. There are also various Java Servlet Filters to accomplish URL Rewriting, should that be more to your liking.
Not that you'd ever make a typo or have to make a fix to your code, but should that happen you can reinitialize Taffy by simply hitting http://example.com/api/index.cfm?reload=true
. You can disable this and/or set a stronger password for it in your Application.cfc file. See the API Docs for more details.
Taffy also ships with an awesome(ly helpful) dashboard that contains a mock client and an instant documentation generator. To access it, simply hit http://example.com/api/index.cfm/?dashboard
. Don't forget to shut this off in a production environment - all of the configuration options are available in the API Docs