Have you ever wanted to create an API for your web site? Many sites do so now - using their API as a way to share their information with others. This allows other web sites, desktop apps, and mobile applications a way to work with and repackage your data. In this blog entry, I'm going to demonstrate how you can use ColdFusion to build a simple API for your site.
Before we begin, there are a few considerations to keep in mind.
First, what you decide to share is less a technical question and more a business question. If your web site is your business, you have to consider if it makes sense for folks to get information without actually visiting your site. Sharing is nice, but it doesn't pay for hosting. You need to look at what your site provides and make a decision on how much you want to share. So for example, a news site could possibly share news within a given time range, perhaps just the past 24 hours. A stock market site could share stock prices, but only those that are one hour old. Or perhaps only the stocks from a certain list.
As I said, this is entirely business level and not technical, but you want to have a good idea in place before you create the API. About one of the worst things you could do as a provider of information is provide a set of data initially and then change your mind later. It happens - and it can't always be avoided, but you want to avoid this at all possible.
Even for folks who aren't in a commercial business at all, you want to be sure that your server can handle the load of hundreds, thousands, maybe millions of people hitting your API. Most of us make use of tools like Google Analytics or Omniture to track our traffic on public pages, but these tools do not work with APIs (not as far as I know). While your site may be get a few hundred hits per week, if you release a popular API it may get a tremendous amount of traffic and you may not be aware of it until your server crawls to a halt.
To summarize - you want to have a good idea of what you will serve and how you will handle the potential traffic.
The good news is that those decisions are the hard ones. Technically, ColdFusion goes a long way to making APIs easy to create.
We've got a few options here on how data can be served to others. You could...
- Create a web service
- Create a simple HTTP based service that spits out JSON
- Expose your data as simple text, perhaps TSV or CSV formatted
- Hell, you could create an API that sends out results via email or IM as well
- Double Hell, you could create an API that sends out results via SMS
For my demo, I'll be making use of ColdFusion Components. As you will see, this will allow me to create both a JSON-based service and a web service all at once. I'm not a big fan of web services, but since ColdFusion makes it so trivial to produce them, there's no reason not to support it. Let's begin by taking a look at the application as it stands now.
Demo(Demo no longer avaialble.)
Sexy demo, right? So obviously there isn't a lot here, but let's go over the base code. First, our Application.cfc:
component {
this.name="cfremoteapidemo";
this.datasource="cfartgallery";
public boolean function onApplicationStart() {
application.artCFC = new model.art();
return true;
}
}
Not much going on here. We set up a few settings and in the application start up, create an Application scope variable for an Art component. This is not a full MVC type application, but I've tried to separate out my business logic from my display as much as possible. In this case, we will have one CFC handling the business logic and database interaction while my CFMs will just do display. Let's look at art.cfc:
component {
public query function getart() {
var q = new com.adobe.coldfusion.query();
q.setSQL("select artid, artname, description, price, largeimage, issold from art");
return q.execute().getResult();
}
}
Again, not much here but a simple query. Now to our front end:
<cfset art = application.artCFC.getart()>
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
<!--[if lt IE 9]><script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
});
</script>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>Art</h1>
</div>
<cfoutput query="art">
<p>
<h3>#artname#</h3>
Price: #dollarFormat(price)# <cfif isBoolean(issold) and issold><span class="label important">Sold</span></cfif><br/>
#description#<br/>
<img src="/cfdocs/images/artgallery/#largeimage#">
</p>
</cfoutput>
</div>
</body>
</html>
Most of the code here is HTML related, but you can see on top where I ask my Application-scoped component for the data and then display it below.
So as I said - a simple application, right? For my API, I want to expose my art data. I can't share my Application-scoped Art component because it's a variable. Technically folks could hit the CFC directly with their browser, but it's got no remote methods so it wouldn't actually expose any functionality. What I'll do instead though is build a new CFC. This CFC will serve as my API. As I add more features to my site, this one CFC will continue to be the core way folks "communicate" with my data.
Here is api.cfc, stored in the root of my application:
component {
remote array function listart() {
//convert to an array of structs
var result = [];
var art = application.artCFC.getArt();
for(var i=1; i<=art.recordCount; i++) {
arrayAppend(result, {"artid"=art.artid[i],
"artname"=art.artname[i],
"description"=art.description[i],
"price"=art.price[i],
"largeimage"=art.largeimage[i],
"issold"=art.issold[i]});
}
return result;
}
}
The API has one function, listart, marked as remote. I could have simply done this:
return application.artCFC.getArt();
But the serialized version of ColdFusion queries is... well not bad... but a bit odd. I'd think most folks would prefer an array of structs. So my code takes the query result and recreates it. That's it.
What's cool (at least to me), is that at this point right now, we have a web service. Any technology that can talk SOAP can hit the WSDL and see this: http://www.raymondcamden.com/demos/2012/jan/18/v1/api.cfc?wsdl. But even better, at the same time, we have a HTTP service that can spit out JSON as well: http://www.raymondcamden.com/demos/2012/jan/18/v1/api.cfc?method=listart&returnformat=json
Now let's take it up a notch and show something with a bit of interactivity. I've created a new version that has a slightly enhanced model:
component {
public query function getart(string search="",string sort="artname",string sortdir="asc") {
var validsortlist = "artname,price";
var q = new com.adobe.coldfusion.query();
var sql = "select artid, artname, description, price, largeimage, issold from art";
if(arguments.search != "") {
sql &= " where artname like :search or description like :search ";
q.addParam(name="search",value="%#arguments.search#%",cfsqltype="cf_sql_varchar");
}
if(!listFindNoCase(validsortlist, arguments.sort)) {
arguments.sort = "artname";
}
if(arguments.sortdir != "asc" && arguments.sortdir != "desc") arguments.sortdir = "asc";
sql &= " order by #arguments.sort# #arguments.sortdir# ";
q.setSQL(sql);
return q.execute().getResult();
}
}
Now my CFC supports searching and sorting. I can then expose that via the API:
component {
remote array function listart(string sort,string sortdir) {
//convert to an array of structs
var result = [];
var art = application.artCFC.getArt(argumentCollection=arguments);
for(var i=1; i<=art.recordCount; i++) {
arrayAppend(result, {"artid"=art.artid[i],
"artname"=art.artname[i],
"description"=art.description[i],
"price"=art.price[i],
"largeimage"=art.largeimage[i],
"issold"=art.issold[i]});
}
return result;
}
}
I've attached a zip of both v1 and v2 applications to this blog entry. In order for them to work you will need to have the cfartgallery datasource present on your system.
Archived Comments
I couldn't believe the timing of this post when I received it in my inbox as I've been working (eg: struggling with SOAP) on this exact thing for last few days. I have a couple observations / questions that have come up during the development of our API as a SOAP web service. It seems the way Axis' handles ColdFusion structures by mapping them to the generic complexType of Map and MapItem or using property components to define custom complexTypes via the cfargument type attribute (eg: type="user[]") wreaks havoc when trying to consume the web service from other languages (PHP,.NET) and even another ColdFusion instance. The native SOAP functionality in CF works great for accepting and returning simple values, but bombs out when trying accept/return custom complex data types such as an array of user structures. (http://tjordahl.blogspot.co....
Due to the many quirks with the current implementation of the SOAP protocol in CF9 via the Axis engine, we have opted for a purely HTTP based service that returns JSON.
Do you see any issues with programming our API to be primarily focused on JSON as our return format and abandoning support for the SOAP protocol?
It seems like there are a lot of CF web service provider examples that focus on retrieving data, but I was curious if you knew of any examples that were focused on building a web service that not only retrieves information, but focuses on accepting information to be saved via SOAP using complexTypes in the arguments as well as the return?
A simple example might be allowing someone to add many members to a group by passing in an array of members (eg: [{"UID"="{guid here}","FName"="John","LName"="Doe","Email": "john@doe.com"},{etc.}])
Then returning an array of those members with their newly primary key or something (eg: [{"UID"="{guid here}","ID"="12445"},{etc.}]
Thanks again for this timely post and would appreciate any insight you might have as I continue to develop our API.
-tim
Great post Ray. And very timely for me as well. We're discussing how best to build out an API for our CMS and the WordPress API with it's simple functions to get page content and so forth is often held up as an example. It would be interesting to see some examples along those lines if you continue with this topic (which I hope you do).
@Tim, no reason you *have* to go with SOAP. Your HTTP API that returns JSON is perfectly valid. You might also want to consider a RESTful API. There's a nice CF framework called Taffy that makes creating RESTful APIs for ColdFusion pretty simple. https://github.com/atuttle/...
Tim, what Rob said. Honest - I just don't think SOAP is worth the complexity. It's nice that my code works as a web service via SOAP w/o me having to worry too much about it, but to be honest, if this were a real API I'd not bother 'advertising' the fact at all.
Nick, could you expand a bit on what you're looking for?
There are valid reasons to use SOAP over REST in some cases, but from what you've written, your API isn't doing anything that would require a SOAP API.
Can you expand on why you would use SOAP?
By the way folks, there was a broken link at the end of the article. Fixed now.
@Rob, thanks for your reply and I will definitely get taffy installed and see what it can do for us. Thanks for the link.
Also I am curious, as Ray asked, could you provide an example as to where SOAP makes more sense over using a RESTful approach?
Probably a dumb question, but is your layout of your CFCs specific to CF versions greater than 8? I've not yet seen a CFC built using non <cf> tags.
Correct - CF9 added script based CFCs. To be clear, you can absolutely do what I did in tag based CFCs too.
Hi Ray. As a follow up, I am talking about internal apis that systems like WordPress and Drupal use. That would be documentation on all the available functions that a developer can use in plugins and themes. Here is the link to the WP api: http://codex.wordpress.org/....
We've built some of these kinds of functions into our CMS for similar purposes, but it is done using CFCs and as a result the code to invoke them is a little more elaborate (e.g. <cfinvoke>, <cfinvokeargument>, etc.) than using simple functions as in WordPress. So, I guess that I am wondering if there is a preference or best practice here. Is it best to stick with the CFC structure or provide functions that are a bit simpler to access (e.g. getpage(12)) without components. I'm not sure if I'm being clear enough here so let me know if you have any follow up questions.
Ah interesting. Um, I guess it depends on how you read "like Wordpress". WP and Drupal support plugins that let you enhance their functionality. That's an API too, but not the type I had meant here. (External client hits your data/service.)
To your second example of 'getpage(12)', how do you imagine that would work in your CMS?