Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Steve 'Cutter' Blades
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Steve 'Cutter' Blades

Using RESTful Controllers In An AngularJS Resource

By
Published in , Comments (20)

As I blogged last week, AngularJS provides the $resource class as an abstraction layer between your client-side code and the server-side API. This makes performing CRUD-style operations across the network fairly easy. But what happens when you need to execute a command on a RESTful resource that falls outside the standard CRUD (ie. Create, Read, Update, Delete) methods? Luckily, AngularJS handles RESTful "controllers" quite nicely.

According to The REST API Design Rulebook by Mark Masse, there are four resource archetypes:

  • Document - A resource representation.
  • Collection - A collection of server-managed data.
  • Store - A collection of client-managed data.
  • Controller - An action to perform.

Document, Collection, and Store are all about representing resources. Controllers, on the other hand, are about mutating resources. You can already use HTTP verbs to mutate a resource; but, sometimes, run-of-the-mill CRUD operations don't quite make sense. Take, as an example, the following resources:

  • /messages
  • /messages/4

Given this Collection of messages (first resource) and an individual message (second resource), how would you go about clearing all messages? Or, how about archiving the given message? CRUD doesn't quite fit the bill. But, Controllers work perfectly for these types of operations:

  • /messages/clear-all
  • /messages/4/archive

Here, we're using the "clear-all" Controller and the "archive" Controller to mutate the Collection and the Document representations, respectively.

When we start using Controllers in our resources, we are now left with a URL schema that has more variability. AngularJS $resource's can handle this; but, you have to get a little "clever" with how you define your parameter bindings.

Pulling the messages concept from above into an AngularJS context, we'd have to define our resource using the following template:

  • /messages/:id/:controller

The problem with this is that there is now a slight ambiguity around the first URL parameter. Is ":id" referring to my Collection-based Controller? Or is it referring to my Document ID?

To clear up this ambiguity, AngularJS allows us to define multiple parameters in the same portion of the URL template:

  • /messages/:listController:id/:docController

Notice that the second item in my URL template contains two parameters:

  • :listController
  • :id

As long as I only use one of these at a time, AngularJS will construct the RESTful resource properly. To demonstrate, I've put together a demo that defines and then invokes the messages resource from above:

<!doctype html>
<html ng-app="Demo">
<head>
	<meta charset="utf-8" />
	<title>Using RESTful Controllers In AngularJS Resources</title>

	<!--
		Because we are working with Resources, we have to load both
		AngularJS and the ngResource module.
	-->
	<script type="text/javascript" src="../angular-1.0.2/angular.js"></script>
	<script type="text/javascript" src="../angular-1.0.2/angular-resource.js"></script>
	<script type="text/javascript">


		// Tell Angular to load the ngResource before loading our
		// custom app module.
		var app = angular.module( "Demo", [ "ngResource" ] );

		// Run this when the app is ready.
		app.run(
			function( $resource ) {


				// When defining the resource, we get a few actions
				// out of the box, like get() and query(), based on
				// standard HTTP verbs. But, we can also use RESTful
				// controller to change resource state using an
				// action that falls outside normal CRUD operations.
				var messages = $resource(
					"./api.cfm/messages/:listController:id/:docController",
					{
						id: "@id",
						listController: "@listController",
						docController: "@docController"
					},
					{
						clear: {
							method: "POST",
							params: {
								listController: "clear-all"
							}
						},
						archive: {
							method: "POST",
							params: {
								docController: "archive"
							}
						}
					}
				);

				// Now that we have our resource defined, let's invoke
				// it with various configurations.

				// GET without ID.
				messages.query();

				// Post with our list controller.
				messages.clear();

				// GET with ID.
				messages.get(
					{
						id: 4
					}
				);

				// POST with our document "controller".
				messages.archive(
					{
						id: 8
					}
				);

			}
		);


	</script>
</head>
<body>
	<!-- Left intentionally blank. -->
</body>
</html>

Notice that my AngularJS resource accounts for two different controllers:

  • :listController - acting on our message collection.
  • :docController - acting on a specific message.

When I invoke the resource methods above, I end up making four requests to the following URLS:

  • GET http://.../api.cfm/messages
  • POST http://.../api.cfm/messages/clear-all
  • GET http://.../api.cfm/messages/4
  • POST http://.../api.cfm/messages/8/archive

As you can see AngularJS properly negotiated the appropriate RESTful URLs. Hella sweet!

If you are interested, here is my test API file:

api.cfm

<!--- Get the raw resourced path that was requested. --->
<cfset resourcePath = cgi.path_info />


<!---
	NOTE: ColdFusion 10 seems to hang until I make a request to get
	the POST body. Not sure why.
--->
<cfif ( cgi.request_method neq "GET" )>

	<cfset requestBody = getHTTPRequestData().content />

</cfif>


<!---
	Identify the type of request that has come in based on the
	pattern of the requested resource path.
--->
<cfif reFind( "^/messages$", resourcePath )>

	<cfset response = "GET without ID." />

<cfelseif reFind( "^/messages/clear-all$", resourcePath )>

	<cfset response = "POST with clear-all Controller." />

<cfelseif reFind( "^/messages/\d+$", resourcePath )>

	<cfset response = "GET with ID." />

<cfelseif reFind( "^/messages/\d+/archive+$", resourcePath )>

	<cfset response = "POST with archive controller" />

<cfelse>

	<cfset response = "Hmm, couldn't match resource." />

</cfif>


<!---
	Serialize the response as JSON. AngularJS will know how to
	parse this.
--->
<cfset serializedResponse = serializeJSON( response ) />

<!--- Add header for debugging the path. --->
<cfheader
	name="X-Debug-Path"
	value="#cgi.path_info#"
	/>

<!--- Stream response back to the client. --->
<cfcontent
	type="application/json"
	variable="#charsetDecode( serializedResponse, 'utf-8' )#"
	/>

For some reason, ColdFusion 10 was hanging on POST requests until I accessed the request body (via the getHttpRequestData() function). This only happened if the request body contained JSON (JavaScript Object Notation) data. If it was empty, the request did not hang. I don't know enough about request processing to know if that makes any sense. I can, however, confirm that ColdFusion 9 does not do this.

Want to use code from this post? Check out the license.

Reader Comments

1 Comments

Thx for this post. It was very helpful; especially this: /messages/:listController:id/:docController. Didn't find any mention of multiple params in the same url portion in the angular docs or anywhere else

15,841 Comments

@Pardeep,

From what I understand, the $resource module (ie. ngResource) is a RESTful wrapper to the underlying $http service. If you want to, you can just reach down and use the $http service directly (very much akin to jQuery's $.ajax() method I believe).

From what I can tell, the $http service returns promise object ( $q ); $resource, on the other hand, returns empty object / array instances.

15,841 Comments

@Atomi,

On the client side, we do security by fact of only certain user interfaces gets rendered for certain users. Beyond that, the client doesn't handle any "smarter" validation. On the server, we then authenticate the user on each request and make sure they have permissions to make the given request.

So, on the client, we have UI-based security; then on the server, we have full, detailed security.

1 Comments

Very clever, this is exactly what I was trying to do, and I just didn't think about adding another optional parameter there. Thanks for getting me un-stuck~

96 Comments

Okay Ben ... You got me hooked on Angular ...

I've been stalking the webs for the last week or so trying to get up to speed on Angular ...

One pattern I've noticed some of the more experienced NIO developers using is wrapping their module initialization into an anon function for encapsulation ...

===============================
https://gist.github.com/4612238
===============================

Personally I'm using Angular MV on the front with Spring 3 MC on the back ...

OOP through and through ...

I'm basically trying to emulate the pattern that LinkedIn is using by having a platform agnostic front-end via json and REST ...

Here's an awesome post on LinkedIn's engineering team discussing how they've embraced progressive advancement through layering all their technologies ...

http://engineering.linkedin.com/frontend/leaving-jsps-dust-moving-linkedin-dustjs-client-side-templates

Hope this ain't too spammy ;-)

15,841 Comments

@Brad,

My pleasure! Glad to help.

@Edward,

As far as wrapping the code in an anonymous function, that's what I do as well when it comes to my real code. I take it is an opportunity to abbreviate the AngularJS library and my app module:

(function( ng, app ){
. . .
})( angular, myAppModule );

angular => ng
myAppmodule => app

I find this makes the subsequent code easier to write :)

I'll have to take a look at the LinkedIn stuff; we connect to an API that is pretty much a REST(ish) API, returns JSON. But, we have two hooks into it for different types of authentication (ex. Cookie vs. Basic Auth).

2 Comments

Hi Ben, trying to be more RESTful:

/messages
/messages/4

Given this Collection of messages (first resource) and an individual message (second resource), how would you go about clearing all messages?

DELETE /messages

Or, how about archiving the given message?

POST /archive
{
id: 4
message: ...
}

Isn't this a better approach?

15,841 Comments

@Manuel,

It's hard to say which is really better. Ultimately, it all comes down to translating values into an HTTP request; then, on the server-side, translating that HTTP request into an action. I've been playing with both kinds of approaches (ie, item-controllers and top-level actions) and,honestly, both seem to work well.

On a somewhat related, but not entirely related not, I will say that creating shorter resources tends to be easier, in my experience. So, imagine that you have users, and users have messages, you might have something like this:

GET /users/:userID/messages/:messageID

... or something like this:

GET /messages/:messageID

... and return the same exact resource, assuming that the messageID is unique within the system (and not just per user).

Assuming you do the appropriate security checks on the server-side in both cases, then the userID in the first resource can make it more "ceremonious" to create a URL in a way that doesn't add all that much more value. As such, while I really liked the structure of the longer URLs, I tend to implement shorter URLs.

1 Comments

In rest, you delete a collection like:
DELETE /messages

This is the expected rest operation to delete a collection.

To archive, you update the state of the object to archive:

POST /messages/1
{ status: "archived" }

2 Comments

@Ben,

thanks for your reply and for your suggestions, I really appreciate them.

@Eric,

yes, setting status: "archived" to a message is definitely a better approach. I'd use PUT to update thought.

1 Comments

@Ben,

I think initially a promise is often returned first but they resolves itself. So in other words, you could just assume it is the data response and, if you tied the response to the model, would then bind to the view.

pls see this line in the $resource docs:

"Class actions return empty instance (with additional properties below). Instance actions return promise of the action."
-RCP

PS - I love your blog!

1 Comments

Very nice article indeed!
I'm experimenting right now building an api in nodejs and a client app in angular and i was wondering, how should i do a REST compliant authentication (login & register) endpoint?

Something like '/user/login' & '/user/register' sound bad to me, as those would be listControllers, while 'login' or 'register' aren't about the user lists but a single (future) user.

In your opinion, should i use separated specific endpoints ('/login','/register') or perhaps add a specific method to $resource and sending a parameter as it's explained in angular docs ('/user?login')?
TIA

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel