Customized Grails Controller for REST

Published on

Update: new code https://gist.github.com/4152409 Grails can do RESTfull easily enough, but I wanted a restful API from grails without throwing out grails scaffolding, So I decided to customize Grails’ controller template.

grails install-templates

then in [app-name]/src/templates/scaffolding/Controller.groovy

import org.springframework.dao.DataIntegrityViolationException import grails.converters.XML import grails.converters.JSON

class ${className}Controller {

static allowedMethods = \[list:'GET',
	show:'GET',
	edit:\['GET', 'POST'\],
	save:'POST',
	update:\['POST','PUT'\],
	delete:\['POST','DELETE'\]
\]

def index() {
	redirect(action: "list", params: params)
}

def list() {
	params.max = Math.min(params.max ? params.int('max') : 50, 200)
	def list = ${className}.list(params)
	def listObject = \[${propertyName}List: list, ${propertyName}Total: ${className}.count()\]
	withFormat {
		html listObject
		json { render list as JSON }
		xml { render listObject as XML }
	}
}

def create() {
	\[${propertyName}: new ${className}(params)\]
}

def save() {
	def ${propertyName} = new ${className}(params)
	if (!${propertyName}.save(flush: true)) {
		withFormat {
			html {render(view: "create", model: \[${propertyName}: ${propertyName}\])}
			json {
				response.status = 403
				render ${propertyName}.errors as JSON
			}
			xml {
				response.status =403
				render ${propertyName}.errors as XML
			}
		}
		return
	}
	flash.message = message(code: 'default.created.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), ${propertyName}.id\])
	withFormat {
		html {
			redirect(action: "show", id: ${propertyName}.id)
		}
		json {
			response.status = 201
			render ${propertyName} as JSON
		}
		xml {
			response.status = 201
			render ${propertyName}.id
		}
	}
}

def show() {
	def ${propertyName} = ${className}.get(params.id)
	if (!${propertyName}) {
		withFormat {
			html {
				flash.message = message(code: 'default.not.found.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id\])
				redirect(action: "list")
			}
			json { response.sendError(404) }
			xml { response.sendError(404) }
		}
		return
	}
	def object = \[${propertyName}: ${propertyName}\]
	withFormat {
		html {object}
		json { render object as JSON }
		xml { render object as XML }
	}
}

def edit() {
	def ${propertyName} = ${className}.get(params.id)
	if (!${propertyName}) {
		flash.message = message(code: 'default.not.found.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id\])
		redirect(action: "list")
		return
	}
	\[${propertyName}: ${propertyName}\]
}

def update() {
	def ${propertyName} = ${className}.get(params.id)
	if (!${propertyName}) {
		withFormat {
			html {
				flash.message = message(code: 'default.not.found.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id\])
				redirect(action:"list")
			}
			json { response.sendError(404) }
			xml { response.sendError(404) }
		}
		return
	}

	if (params.version) {
		def version = params.version.toLong()
		if (${propertyName}.version > version) {
			${propertyName}.errors.rejectValue("version", "default.optimistic.locking.failure",
					  \[message(code: '${domainClass.propertyName}.label', default: '${className}')\] as Object\[\],
					  "Another user has updated this ${className} while you were editing")
			withFormat {
				html {render(view: "edit", model: \[${propertyName}: ${propertyName}\])}
				json { response.sendError(409) }
				xml { response.sendError(409) }
			}
			return
		}
	}

	${propertyName}.properties = params

	if (!${propertyName}.save(flush: true)) {
		withFormat {
			html {render(view: "edit", model: \[${propertyName}: ${propertyName}\])}
			json {
				response.status = 403
				render ${propertyName}.errors as JSON
			}
			xml {
				response.status = 403
				render ${propertyName}.errors as XML
			}
		}
		return
	}
	withFormat {
		html {
			flash.message = message(code: 'default.updated.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), ${propertyName}.id\])
			redirect(action: "show", id: ${propertyName}.id)
		}
		json {
			response.status = 204
			render ${propertyName} as JSON
		}
		xml {
			response.status = 204
			render ''
		}
	}
}

def delete() {
	def ${propertyName} = ${className}.get(params.id)
	if (!${propertyName}) {
		withFormat {
			html {
				flash.message = message(code: 'default.not.found.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id\])
				redirect(action: "list")
			}
			json { response.sendError(404) }
			xml { response.sendError(404) }
		}
		return
	}
	try {
		${propertyName}.delete(flush: true)
		withFormat {
			html {
				flash.message = message(code: 'default.deleted.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id\])
				redirect(action: "list")
			}
			json {
				response.status = 204
				render ''
			}
			xml {
				response.status = 204
				render ''
			}
		}
	}
	catch (DataIntegrityViolationException e) {
		withFormat {
			html {
				flash.message = message(code: 'default.not.deleted.message', args: \[message(code: '${domainClass.propertyName}.label', default: '${className}'), params.id\])
				redirect(action: "show", id: params.id)
			}
			json { response.sendError(500) }
			xml { response.sendError(500) }
		}
	}
}

}

then modify your UrlMappings.groovy file to look something like this:

class UrlMappings {

static mappings = {
	"/$controller/$action?/$id?"{
		constraints {
		// apply constraints here
		}
	}
	name api0: "/api/$controller/$id"(parseRequest:true){
		action = \[GET: "show", PUT: "update", DELETE: "delete"\]
		constraints {
			id(matches:/\\d+/)
		}
	}

	name api1: "/api/$controller"(parseRequest:true){
		action = \[GET: "list", POST: "save"\]
	}
}

}

I found the JSON Restful plugin a while after I already did most of this, so that is another way to go. Other than it having some issues in grails 2 right now it looks like a really good option, but one thing I like about my approach is that if you want to customize your API in fine detail you can do so. You can always generate individual controllers and modify them of course. Another option is registering custom marshallers. The simple example for customizing your JSON output without changing any controller code would be doing something like this in your Bootstrap.groovy

import grails.converters.JSON class BootStrap { def init = {servletContext ->; JSON.registerObjectMarshaller(Person) { def returnArray = [:] returnArray[‘name’] = it.name returnArray[‘addrs’] = it.addresses return returnArray } … }

2020 update

Grails has really good REST API stuff baked in for a long time now. don’t use this approach above.