Search

Dark theme | Light theme

December 9, 2013

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
<?xml version="1.0" encoding="UTF-8"?>
<list>
  <book>
    <isbn>
      0451169514
    </isbn>
    <title>
      It
    </title>
  </book>
  <book>
    <isbn>
      0307743683
    </isbn>
    <title>
      The stand
    </title>
  </book>
</list>

$ 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