If you've ever needed to create an XML or JSON API for your Wheels application,
you may have needed to go down the path of creating a separate controller or
separate actions for the new format. This introduces the need to duplicate model
calls or even break them out into a super long list of before filters. With
this, your controllers can get pretty hairy pretty fast.
Using a few CFWheels functions, you can easily respond to requests for HTML, XML,
JSON, and PDF formats without adding unnecessary bloat to your controllers.
With CFWheels Provides functionality in place, you can request different formats
using the following methods:
- URL Variable
- URL Extension
- Request Header
Which formats you can request is determined by what you configure in the
controller. See the section below on Responding to Different Formats in the
Controller for more details.
CFWheels will accept a URL variable called
format. If you wanted to request the
XML version of an action, for example, your URL call would look something like
The same would go for JSON:
Perhaps a cleaner way is to request the format as a "file" extension. Here are
the XML and JSON examples, respectively:
This works similarly to the URL variable approach mentioned above in that
there will now be a key in the
params struct set to the
With the XML example, there will be a variable at
params.format with a value
If you are calling the CFWheels application as a web service, you can also request
a given format via the HTTP
If you are consuming the service with another CFWheels application, your
<cfhttp> call would look something like this:
In this example, we are sending an
Accept header with the value for the
<cfhttp url="http://www.example.com/products"> <cfhttpparam type="header" name="Accept" value="application/octet-stream"> </cfhttp>
Here is a list of values that you can grab from mimeTypes() with CFWheels out
of the box.
You can use addFormat() to set more types to the appropriate MIME type for
reference. For example, we could set a Microsoft Word MIME type in
config/settings.cfm like so:
<cfset addFormat(extension="doc", mimeType="application/msword")>
The fastest way to get going with creating your new API and formats is to call
provides() from within your controller's
Take a look at this example:
<cfcomponent extends="Controller"> <cffunction name="init"> <cfscript> provides("html,json,xml"); </cfscript> </cffunction> <cffunction name="index"> <cfscript> products = model("product").findAll(order="title"); renderWith(products); </cfscript> </cffunction> </cfcomponent>
By calling the provides() function in
init(), you are instructing the
CFWheels controller to be ready to provide content in a number of formats.
Possible choices to add to the list are
html (which runs by default),
This is coupled with a call to renderwith() in the following actions.
In the example above, we are setting a query result of products and passing it
to renderwith(). By passing our data to this function, CFWheels gives us the
ability to respond to requests for different formats, and it even gives us the
option to just let CFWheels handle the generation of certain formats
When CFWheels handles this response, it will set the appropriate MIME type in the
Content-Type HTTP header as well.
Responding to requests for the HTML version is the same as you're already used
to with Rendering Content. renderwith() will accept the same arguments
as renderPage(), and you create just a view template in the
If the requested format is
json, the renderwith() function will
automatically transform the data that you provide it. If you're fine with what
the function produces, then you're done!
Best Practices for Providing JSON
Unfortunately there have been a lot of JSON related issues in CFML over the years. To avoid as many of these problems as possible we thought we'd outline some best practices for you to follow.
First of all, always return data as an array of structs. This is done by using the
returnAs argument (on findAll() for example), like this:
authors = model("author").findAll(returnAs="structs");
The reason for doing it this way is that it will preserve the case for the struct / JSON keys.
Secondly, make use of CFWheels ability to return the JSON values in a specified type. This is done in the renderWith() function, like this:
renderWith(data=authors, firstName="string", booksForSale="integer");
With that in place you can be sure that
firstName will always be treated as a string (i.e. wrap in double quotes) and
booksForSale as an integer (i.e. no decimal places) when producing the JSON output. Without this, your CFML engine might guess what the data type is, and it wouldn't always be correct unfortunately.
If you need to provide content for another type than
json, or if you
need to customize what your CFWheels application generates, you have that option.
In your controller's corresponding folder in
views, all you need to do is
implement a view file like so:
If you need to implement your own XML- or JSON-based output, the presence of
your new custom view file will override the automatic generation that CFWheels
Example: PDF Generation
If you need to provide a PDF version of the product catalog, the view file at
views/products/index.pdf.cfm may look something like this:
<cfdocument format="pdf"> <h1>Products</h1> <table> <thead> <tr> <th>Name</th> <th>Description</th> <th>Price</th> </tr> </thead> <tbody> #includePartial(products)# </tbody> </table> </cfdocument>