Grails Goodness: Rendering Partial RESTful Responses
Grails 2.3 added a lot of support for RESTful services. For example we can now use a respond()
method in our controllers to automatically render resources. The respond()
method accepts a resource instance as argument and a map of attributes that can be passed. We can use the includes
and excludes
keys of the map to pass which fields of our resources need to be included or excluded in the response. This way we can render partial responses based on a request parameter value.
First we start with a simple domain class Book
:
// File: grails-app/domain/com/mrhaki/grails/rest/Book.groovy
package com.mrhaki.grails.rest
class Book {
String title
String isbn
int numberOfPages
}
Next we create a controller BookApiController
, which we extend from the RestfulController
so we already get a lot of basic functionality to render an instance of Book
. We overwrite the index()
and show()
methods, because these are used to display our resource. We use the request parameter fields
to define a comma separated list of fields and pass it to the includes
attribute of the map we use with the respond()
method. Notice we also set the excludes
attribute to remove the class
property from the output.
// File: grails-app/controllers/com/mrhaki/grails/rest/BookApiController.groovy
package com.mrhaki.grails.rest
import grails.rest.RestfulController
class BookApiController extends RestfulController<Book> {
// We support both JSON and XML.
static responseFormats = ['json', 'xml']
BookApiController() {
super(Book)
}
@Override
def show() {
// We pass which fields to be rendered with the includes attributes,
// we exclude the class property for all responses.
respond queryForResource(params.id), [includes: includeFields, excludes: ['class']]
}
@Override
def index(final Integer max) {
params.max = Math.min(max ?: 10, 100)
respond listAllResources(params), [includes: includeFields, excludes: ['class']]
}
private getIncludeFields() {
params.fields?.tokenize(',')
}
}
Next we define a mapping to our controller in UrlMappings.groovy
:
// File: grails-app/conf/UrlMappings.groovy
class UrlMappings {
static mappings = {
...
"/api/book"(resources: "bookApi")
...
}
}
We are now almost done. We only have to register a new Spring component for the collection rendering of our Book
resources. This is necessary to allow the usage of the includes
and excludes
attributes. These attributes are passed to the so-called componentType of the collection. In our case the componentType is the Book
class.
// File: grails-app/conf/spring/resources.groovy
import com.mrhaki.grails.rest.Book
import grails.rest.render.json.JsonCollectionRenderer
import grails.rest.render.xml.XmlCollectionRenderer
beans = {
// The name of the component is not relevant.
// The constructor argument Book sets the componentType for
// the collection renderer.
jsonBookCollectionRenderer(JsonCollectionRenderer, Book)
xmlBookCollectionRenderer(XmlCollectionRenderer, Book)
// Uncomment the following to register collection renderers
// for all domain classes in the application.
// for (domainClass in grailsApplication.domainClasses) {
// "json${domainClass.shortName}CollectionRenderer(JsonCollectionRenderer, domainClass.clazz)
// "xml${domainClass.shortName}CollectionRenderer(XmlCollectionRenderer, domainClass.clazz)
// }
}
Now it is time to see our partial responses in action using some simple cURL invocations:
$ curl -X GET -H "Accept:application/json" http://localhost:9000/custom-renderers/api/book?fields=title
[
{
"title": "It"
},
{
"title": "The stand"
}
]
$ curl -X GET -H "Accept:application/xml" http://localhost:9000/custom-renderers/api/book?fields=title,isbn
0451169514
It
0307743683
The stand
$ curl -X GET -H "Accept:application/json" http://localhost:9000/custom-renderers/api/book/1
{
"id": 1,
"isbn": "0451169514",
"numberOfPages": 1104,
"title": "It"
}
$ curl -X GET -H "Accept:application/json" http://localhost:9000/custom-renderers/api/book/1?fields=isbn,id
{
"id": 1,
"isbn": "0451169514"
}
Code written with Grails 2.3.2