REST uses the verbs built right into HTTP as the way to define an action to take upon the destination resource, as denoted in the URI. The Grails conventions fit very nicely into the way humans think of REST, and with a combination of UrlMappings and the "withFormat" command, you can easily extend a Grails Controller to accommodate non-HTML formats. We'll get into it after the jump.
In part 1, I'm going to focus on HTTP GET. In part 2, I'll expand out to PUT, DELETE, and POST.
We will start with a simple controller with the standard "list/save/update/delete" methods. I'm not going to put all the code here; you can pull the code down from github to inspect everything. Let's focus first on the
list
method:def list = { params.max = Math.min(params.max ? params.int('max') : 10, 100) [cowInstanceList: Cow.list(params), cowInstanceTotal: Cow.count()] }
A list is a request for all of the resources of a particular type. We can define our API to say that
/rest/cow/list
will return a list of Cows. The UrlMapping for that is:"/rest/$controller/list" { action = [GET:"list"] }
A GET at our URI will be translated to the
list
method. Notice we're using the $controller
placeholder to capture the "cow" part of the URL. This prepares us for any other REST controllers in our application. But right now, the list method only returns HTML. We want to support something more machine readable, don't we? With the addition of a small amount of code, we can export JSON and XML. def list = { params.max = Math.min(params.max ? params.int('max') : 10, 100) withFormat { html { [cowInstanceList: Cow.list(params), cowInstanceTotal: Cow.count()] } xml { render Cow.list(params) as XML } json { render Cow.list(params) as JSON } } }
Now, this code is assuming that the domain class maps very well to the outside expectations of a Cow. If that's not the case, then you would need to either convert the Cow to a different class or create a custom formatter that would export only parts of the Cow. (We won't go into detail about the parts of a cow, there are plenty of articles about that already.) Perhaps we'll return to custom formatters in a later post.
So, we have quickly created a method that will provide XML or JSON representations of all the Cow instances in our database. Why was it so easy? First, Grails already understands JSON and XML; the creators of Grails knew you'd be wanting it sooner or later. The content type can be specified by the caller either via
Accepts=application/json
HTTP header, or via an extension to the URI (see below). Either of those things will set the format
parameter, which the withFormat
method uses to determine which formatting closure to call.We can try out the new method, right from our browser:
- http://localhost:8080/moo/cow/list will return HTML.
- http://localhost:8080/moo/cow/list.xml will return XML.
- http://localhost:8080/moo/cow/list.json will return JSON.
show
. The show
method returns a single element, not a list. Therefore, we're going to need the ID of the value to be retrieved. Our URL mapping for REST for elements is:"/rest/$controller/element/$id"{ action = [GET:"show"] }
Our original
show
method looked like this:def show = { def cowInstance = Cow.get(params.id) if (!cowInstance) { flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'cow.label', default: 'Cow'), params.id])}" redirect(action: "list") } else { [cowInstance: cowInstance] } }
Which is, "get the given record; if it doesn't exists, tell the user it was not found, else, pass it along to the view to be rendered." Here's the functionality with the REST-y code sprinkled in.
def show = { def cowInstance = Cow.get(params.id) if (!cowInstance) { withFormat renderNotFound } else { withFormat { html { [cowInstance: cowInstance] } xml { render cowInstance as XML } json { render cowInstance as JSON } } } }
Same flow! Notice the
renderNotFound
block. That's a general purpose closure I've put somewhere else, so it can be reused in the other methods.def renderNotFound = { html { flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'cow.label', default: 'Cow'), params.id])}" redirect(action: "list") } xml { response.status = 404 render "Cow not found." } json { response.status = 404 render "Cow not found." } }
response.status = 404
? Exactly! Perfect way to tell our mechanical caller the same thing that default.not.found.message
is telling the user. After all, doesn't a machine deserve to be treated like a user? (Okay, maybe not...)These too can be attempted directly from the browser. Assuming there is something saved in your app (like via the HTML create page), then:
- http://localhost:8080/moo/cow/show/1 will return HTML.
- http://localhost:8080/moo/cow/show/1.xml will return XML.
- http://localhost:8080/moo/cow/show/1.json will return JSON.
And be sure to try accessing an ID that doesn't exist.
So, what I've said not too briefly is that it can be easy to start incorporating REST interfaces into your standard Grails controllers. In most cases, there is no need for different methods, or separate controllers altogether (gasp!). In part 2 of this series, I'll talk about implementing
save
, update
& delete
. They are far more interesting, as well as far more verbose.
Part 2 is available; it deals with PUT, POST, and DELETE.
ReplyDeletegreat blog post. Thank you
ReplyDeletegreat! thanks!
ReplyDelete