Using REST Services in ColdFusion 10

On February 20, 2012, in ColdFusion, REST, by Anuj Gakhar

ColdFusion 10 went into public beta a couple of days ago and I installed it on a Windows 7 VM, to test it out. There are a LOT of new features in this release and I am sure the next few weeks will be pretty busy in the ColdFusion blogosphere, with posts coming from all the CF bloggers on the usage of the new features. However, the very first feature that I wanted to test out was the RESTful Services.

A couple of months ago, I wrote a blog post on my thoughts on how to design a RESTful API. So, I decided to follow the same rules for this as well. For those who want to jump straight to the code, the code for my sample REST Service is available on github. Feel free to fork, raise issues etc. For those who want to know the steps I followed, read on…

First of all, I created a very simple table called “tbluser” with a few fields (firstname, lastname, username, email) and then I created a User CFC for this, which looks like this :-

component accessors="true" output="false"      
{
	property name="id" type="numeric"  ;
	property name="username" type="string";
	property name="firstname" type="string";
	property name="lastname" type="string";
	property name="email" type="string";
}

My Application.cfc looks like this :-

component output="false"
{
	this.name = "RestSample";
	this.applicationTimeout = createTimespan(0,2,0,0);
	this.datasource = "restsample";
	
	this.restsettings.skipCFCWithError = true;
	
	public boolean function onRequestStart()
	{
		if(structKeyExists(url, "refreshRestServices"))
		{
			restInitApplication(getDirectoryFromPath(getCurrentTemplatePath()), this.name);
		}
		
		return true;
	}
}

Notice, I am making use of the new ColdFusion 10 function – restInitApplication(), this function registers the REST Service with the CF server. If the code in the service changes, you will need to refresh the service registration and with the above code, that’s now doable with a URL query parameter. Also, worth noting is that, the same service registration can also be done in the CF administrator under the “Data & Services” -> “REST Services” section. restInitApplication() takes it’s first argument as the root directory where it should look for REST enabled services. Basically, you can have as many REST services as you want under this root directory. I decided to call my service UserService. Here is how it looks like :-

component restpath="users" rest="true"
{
	import model.User;
	
	remote Array function getUsers() httpmethod="GET" 
	{
		var response = [];
		var qry = new Query();
		var userQry = "";
		
		qry.setSQl("select * from tbluser");
		userQry = qry.execute().getResult();
		
		if(userQry.recordcount)
		{
			for(i = 1; i lte userQry.recordcount; i++)
			{
				objUser = populateUser(userQry, i);
				arrayAppend(response, objUser);
			}
		}
		
		return response;
	}  
	
	remote User function getUser(numeric userid restargsource="Path") httpmethod="GET" restpath="{userid}" 
	{
		var response = "";
		var qry = new Query();
		var userQry = "";
		
		qry.setSQl("select * from tbluser where id = :userid");
		qry.addParam(name="userid", value="#arguments.userid#", cfsqltype="cf_sql_numeric");
		userQry = qry.execute().getResult();
		
		if(userQry.recordcount)
		{
			response = populateUser(userQry, 1);
		} else {
			throw(type="Restsample.UserNotFoundError", errorCode='404', detail='User not found');
		}
		
		return response;
	}  
	
	remote any function deleteUser(numeric userid restargsource="Path") httpmethod="DELETE" restpath="{userid}" 
	{
		var response = "";
		var qry = new Query();
		var userQry = "";
		
		qry.setSQl("delete from tbluser where id = :userid");
		qry.addParam(name="userid", value="#arguments.userid#", cfsqltype="cf_sql_numeric");
		userQry = qry.execute().getPrefix();
		
		if(userQry.recordcount)
		{
			response = "User Deleted";
		} else {
			throw(type="Restsample.UserNotFoundError", errorCode='404', detail='User not found');
		}
		
		return response;
	}  
	
	remote any function createUser(
			required string username restargsource="Form",
			required string firstname restargsource="Form", 
			required string lastname restargsource="Form", 
			required string email restargsource="Form") httpmethod="POST"
	{ 
		var response = "";
		var qry = new Query();
		var userQry = "";
		
		qry.setSQl("insert into tbluser (username, firstname, lastname, email) VALUES (:username, :firstname, :lastname, :email)");
		qry.addParam(name="username", value="#arguments.username#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="firstname", value="#arguments.firstname#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="lastname", value="#arguments.lastname#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="email", value="#arguments.email#", cfsqltype="cf_sql_varchar");
		userQry = qry.execute().getPrefix();
		
		if(userQry.recordcount)
		{
			response = "User Created";
		} else {
			throw(type="Restsample.UnKnownError", detail='Unknown error occured');
		}
		
		return response;
	}  
	
	remote any function updateUser(
			required string username restargsource="header",
			required string firstname restargsource="header", 
			required string lastname restargsource="header", 
			required string email restargsource="header",
			required numeric userid restargsource="Path") httpmethod="PUT" restpath="{userid}" 
	{ 
		var response = "";
		var qry = new Query();
		var userQry = "";
		
		qry.setSQl("update tbluser set username = :username, firstname = :firstname, lastname = :lastname, email = :email where id = :userid");
		qry.addParam(name="username", value="#arguments.username#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="firstname", value="#arguments.firstname#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="lastname", value="#arguments.lastname#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="email", value="#arguments.email#", cfsqltype="cf_sql_varchar");
		qry.addParam(name="userid", value="#arguments.userid#", cfsqltype="cf_sql_numeric");
		userQry = qry.execute().getPrefix();
		
		if(userQry.recordcount)
		{
			response = "User Updated";
		} else {
			throw(type="Restsample.UnKnownError", detail='Unknown error occured');
		}
		
		return response;
	} 
	
	private User function populateUser(query qry, numeric rowNumber)
	{
		var response       = createObject("component", "model.User");
		response.id  	   = qry.id[rowNumber];
		response.username  = qry.username[rowNumber];
		response.firstname = qry.firstname[rowNumber];
		response.lastname  = qry.lastname[rowNumber];
		response.email     = qry.email[rowNumber];
		return response;
	}
}
	

To make a service REST enabled, you need to give it a “restpath” as one of the component attributes. I have given my service “restpath=”users”. This allows me to call the service at the following URL :-

http://127.0.0.1:8500/rest/RestSample/users

In the above URL, “rest” is the reserved word used by CF to identify it as a REST URL. “RestSample” is the service mapping name I used when I called restInitApplication() and “users” is the restpath I used my UserService.cfc. I have written down functions for GET, POST, PUT and DELETE methods which allow selection, insertion, updates and deletes on the User object.

Here are some of the cool things :-

//get all users in JSON format

http://127.0.0.1:8500/rest/RestSample/users.json

//get all users in XML format

http://127.0.0.1:8500/rest/RestSample/users.xml

//get a single user in JSON format

http://127.0.0.1:8500/rest/RestSample/users/{userid}.json

//get a single user in XML format

http://127.0.0.1:8500/rest/RestSample/users/{userid}.xml

//delete a user
http://127.0.0.1:8500/rest/RestSample/users/{userid} (HTTP DELETE request)

You get the idea….CF does automatic JSON and XML serialization based on the extension. You can read more on how that’s handled here.

There are a few new additions to cfargument, that need to be noted here. restargsource – which tells CF where it needs to look for the argument. In my getUser() function, I am using restargsource=”Path” which makes URLs like http://127.0.0.1:8500/rest/RestSample/users/2.json possible, because it tells CF to read the argument value from the Path.

Overall, I think there are a few things to grasp here, but once you get a hold of the basic concept, this is pretty cool stuff. Specially, the automatic JSON and XML serialization.

I am going to extend my sample with some complex objects and making use of some nested CFCs see how that works out. But until, then, enjoy RESTing!

Tagged with:  

17 Responses to Using REST Services in ColdFusion 10

  1. Adam Tuttle says:

    When I use the url parameter to refresh (your 2nd code block, lines 11-14), CF returns error code 500 instead of my service. This is with a simple hello world type example. The only way I seem to be able to get my services to refresh is through the administrator.

    • Anuj Gakhar says:

      If it refreshes from the administrator, it should refresh from the code as long the service has no errors. Try deleting the service from the administrator and then refresh the code, if it successfully registers, it will appear in the administrator anyways.

      • Adam Tuttle says:

        If I remove it from the administrator, how will it know where to find my service? The service mapping points to the application folder…. Even if I used the application name, which I’m not, that still wouldn’t be enough information for CF to find the code.

        • Anuj Gakhar says:

          In my code here :-

          restInitApplication(getDirectoryFromPath(getCurrentTemplatePath()), this.name);

          I am using the current directory as the root folder (first argument), which is where CF will search for any REST enabled services. and the second argument can be any name you want. I just chose to use this.name.

          • Adam Tuttle says:

            Since a CFM is not being called directly, and the path does not match the file system, ColdFusion can’t route the request (because it’s not defined in the administrator).

            The ONLY way that I see this as possible is if you have non-REST code, eg an index.cfm somewhere, and you call THAT and reinitialize the rest application. That does not suit my purposes, my application is api-only.

            It would seem, then, that I have no choice but to use the refreshing tool within the CF Administrator. I could create an index.cfm that does the refresh and use that instead, but they are functionally equivalent so why waste my time?

          • Anuj Gakhar says:

            Hi Adam,

            A .cfm does not to be called because the restInitApplication() code sits in the Application.cfc’s onRequestStart() method. And even if you app is api-only , it still would have a Application.cfc in any case. So, if your app sits in a folder called “myapp”, you would then call http://localhost/myapp?refreshRESTServices=true to refresh your services and once that’s done, you can then call the actual service via http://localhost/rest/{MyMappingName}/{MyServiceRestpath}

            That’s what I did and it worked for me. But yes, doing it from the administrator works too. I didn’t want to switch to and fro between browser windows to keep refreshing my services. But both ways it works.

          • Anuj Gakhar says:

            Ah, I see what you mean. Without an index.cfm, the Application.cfc code may never run. That makes sense.

          • Adam Tuttle says:

            I was expecting to be able to use this from a REST call to refresh the service, much in the same way that we currently use URL parameters to reset an application’s variables if the config has changed.

            I find it frustrating that we can’t do that, and honestly, I’m frustrated that we have to refresh at all. I’d love to find out the technical limitations that require it.

          • Anuj Gakhar says:

            Yeah, it is indeed frustrating having to refresh at all. I assume CF keeps a cached copy of the service to be able to serve all the GET,POST,PUT,DELETE methods. But as you said, I’d love to find out too about what’s going on behind the scenes here.

  2. Tom Kirstein says:

    I agree. This is frustrating enough I’d almost consider it a bug. I appreciate the tip on using code to refresh the REST service though!

  3. Jo Mamma says:

    What would it look like to call the update user methods?http://127.0.0.1:8500/rest/RestSample/users/{userid} with the method being “PUT”? Just wanted to clarify.

  4. Stephane says:

    Hi,
    First, thanks for your code !
    when I call in get the main function /users :
    http://127.0.0.1:8500/rest/RestSample/users
    the json sent back looks like :
    [
    {.....},
    {.....}
    ]

    I would like to get this kinf of feedback :

    {
    “users” : [
    {.....},
    {.....}
    ]
    }

    to comply with this : http://www.androidhive.info/2012/01/android-json-parsing-tutorial/

    I don’t get how to manage this ….

    thanks for any help !

    Stéphane

  5. Anurag says:

    Is there a way to consume the above restful service through ajax and not from the cfm page. If yes how to call get and post for the above cases.

Leave a Reply

© 2011 Anuj Gakhar
%d bloggers like this: