CF101: Adding an API to your site

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 </ul>

    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;	
    	}
    
    }
    

    You can see this running here: http://www.raymondcamden.com/demos/2012/jan/18/v2/api.cfc?method=listart&returnformat=json&sort=price&sortdir=desc This URL lists art by price with the most expensive items first.

    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.

    Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support.

Lafayette, LA https://www.raymondcamden.com

Comments