13. Web Services

Web services are all about providing a web API onto your web application and are typically implemented in either SOAP or REST.

13.1 REST

REST is not really a technology in itself, but more an architectural pattern. REST is extremely simple and just involves using plain XML or JSON as a communication medium, combined with URL patterns that are "representational" of the underlying system and HTTP methods such as GET, PUT, POST and DELETE.

Each HTTP method maps to an action. For example GET for retrieving data, PUT for creating data, POST for updating and so on. In this sense REST fits quite well with CRUD.

URL patterns

The first step to implementing REST with Grails is to provide RESTful URL mappings:

static mappings = {
       action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]

Here we have used the capability of URL Mappings to map to HTTP methods to provide a RESTful API to our controller. Each HTTP method such as GET, PUT, POST and DELETE map to unique actions within the controller.

XML Marshaling - Reading

The controller implementation itself can use Grails' XML marshaling support to implement the GET method:

import grails.converters.*
class ProductController {
	def show = {
		if(params.id && Product.exists(params.id)) {
			def p = Product.findByName(params.id)
			render p as XML
		else {
			def all = Product.list()
			render all as XML

Here what we do is if there is an id we search for the Product by name and return it otherwise we return all Products. This way if we go to /products we get all products, otherwise if we go to /product/MacBook we only get a MacBook.

XML Marshalling - Updating

To support updates such as PUT and POST you can use the params object which Grails enhances with the ability to read an incoming XML packet. Given an incoming XML packet of:

<?xml version="1.0" encoding="ISO-8859-1"?>
	<vendor id="12">

You can read this XML packet using the same techniques described in the Data Binding section via the params object:

def save = {
	def p = new Product(params['product'])

if(p.save()) { render p as XML } else { def errors = p.errors.allErrors.collect { g.message(error:it) } render(contentType:"text/xml") { error { for(err in errors) { message(error:err) } } } } }

In this example by indexing into the params object using the key 'product' we can automatically create and bind the XML using the constructor of the Product class. An interesting aspect of the line:

def p = new Product(params['product'])
Is that it requires no code changes to deal with a form submission that submits form data than it does to deal with an XML request. The exact same technique can be used with a JSON request too.

If you require different responses to different clients (REST, HTML etc.) you can use content negotation

The Product object is then saved and rendered as XML, otherwise an error message is produced using Grails' validation capabilities in the form:

   <message>The property 'title' of class 'Person' must be specified</message>

13.2 SOAP

Grails supports SOAP through the XFire plug-in which uses the popular XFire SOAP stack to integrate SOAP support into Grails. The XFire plug-in allows you to expose Grails services as SOAP services using a special expose property:

class BookService {

static expose=['xfire']

Book[] getBooks(){ Book.list() as Book[] } }

The WSDL can then be accessed at the location:

For more information on the XFire plug-in refer the documentation on the wiki.

13.3 RSS and Atom

No direct support is provided for RSS or Atom within Grails. You could construct RSS or ATOM feeds with the render method's XML capability. There is however a Feeds plug-in available for Grails that provides a RSS and Atom builder using the popular ROME library. An example of its usage can be seen below:

def feed = {
    render(feedType:"rss", feedVersion:"2.0") {
        title = "My test feed"
        link = "http://your.test.server/yourController/feed"

Article.list().each() { entry(it.title) { link = "http://your.test.server/article/${it.id}" it.content // return the content } } } }