Several weeks ago I have posted my experiences with JAX-RS. Later on, as I read and understood more about Grails, I wanted to perform a similar CRUD exercise with Grails. Grails supports REST out of the box. If somebody says that Grails rocks, you don't have to take them at their word but try out a few examples like this (and eventually agree with them!).
Obviously, REST is lot more than CRUD, and an excellent architectural pattern in my opinion. But for this post we will restrict ourselves to the CRUD aspect.
Get started
Downloading and setting up Grails is out of scope for this post. Refer to Grails website for that. Once you have Grails setup run
grails create-app [app-name]
Replace [app-name] with what ever you want to name your app. This command should create a standard Grails project structure. We will use the embedded database (HSQLDB) and embedded servlet container (Jetty). Grails ships with them auto-configured. So use all defaults for this exercise.
Resource
The resource we will be dealing here is a simple Customer object with firstName, lastName and zipcode properties. Declare Customer.groovy in grails-app/domain.
class Customer {
String firstName
String lastName
String zipcode
static constraints = {
firstName(blank: false)
lastName(blank: false)
zipcode(blank: false)
}
}
All the properties are made mandatory, as specified in the constraints above.
URL Mappings
URL mappings in Grails is an extremely useful and powerful feature. We can set a URL mapping's action parameter to a map that associates HTTP methods with action names. UrlMappings.groovy will go into grails-app/conf
class UrlMappings {
def mappings = {
"/customer" (controller: "customer") {
action = [GET:"list", POST: "create"]
}
"/customer/$id" (controller: "customer") {
action = [GET: "show", PUT:"update", DELETE:"delete"]
}
}
}
Here we have mapped /customer to a corresponding controller (we still have to work on the Controller, coming next), and the actions are mapped such that GET requests go to list action of the controller and POST is forwarded to save action. For a different URL pattern /customer/$id we have also provided mappings for its supported HTTP methods: GET, PUT and DELETE.
Controller
Create CustomerController.groovy in grails-app/controller, and let's start working on CRUD operations on our resource.
Create
import grails.converters.XML
class CustomerController {
def create = {
def xml = request.XML
def customer = new Customer()
customer.firstName = xml.firstName.text()
customer.lastName = xml.lastName.text()
customer.zipcode = xml.zipcode.text()
if (customer.validate()) {
customer.save();
response.status = 201
render customer as XML
} else {
sendValidationFailedResponse(customer, 403)
}
}
private def sendValidationFailedResponse(customer, status) {
response.status = status
render contentType: "application/xml", {
errors {
customer?.errors?.fieldErrors?.each {err ->
field(err.field)
message(g.message(error: err))
}
}
}
}
}
- See the power of Groovy, the ease with which you can navigate XML. Populate the Customer object and validate it for constraints defined in the domain object.
- If the validation is passed save the resource, set the status and respond back with the created resource (with id). In this context, see line 14. It can't get any simpler than that. A domain object is transformed to XML with just that call.
- Grails ships with two converters XML and JSON. If you want JSON response above just replace line 14 with render customer as JSON
- rest of the code is status setting and error handling, a display of Groovy closures.
Test it
First, run your app from the command line --
grails run-app
Rest-client is an excellent REST client, I strongly recommend it for standalone testing. It has got a nice little UI and works as advertised with little fuss.
(Click on the image to enlarge)
Read
def list = {render Customer.list() as XML}
def show = {
Customer customer = Customer.get(params.id)
if (customer) {
render customer as XML
} else {
SendNotFoundResponse()
}
}
private def SendNotFoundResponse() {
response.status = 404
render contentType: "application/xml", {
errors {
message("Customer not found with id: " + params.id)
}
}
}
- It doesn't get much simpler than that. Two actions list and show display XML but act on two different URL patterns (see URL mappings). Former lists all the customers in the database and the latter provide details of a specific customer (with a given id).
- Note that id portion is passed on using params.id field
- Status code 404 is sent back, see SendNotFoundResponse. Response is sent back in XML.
Test it
GET is easy to test in a browser, or use Rest-client to test. See the URL, client is requesting for a resource with an Id.
(Click on the image to enlarge)
Update
Update and Delete are very similar to the code described above. The following should be self explanatory by now.
def update = {
Customer customer = Customer.get(params.id)
if (!customer) {
SendNotFoundResponse()
}
def xml = request.XML
customer.firstName = xml.firstName.text()
customer.lastName = xml.lastName.text()
customer.zipcode = xml.zipcode.text()
if (customer.validate()) {
customer.save();
response.status = 201
render ""
} else {
sendValidationFailedResponse(customer, 403)
}
}
Delete
def delete = {
Customer customer = Customer.get(params.id)
if (!customer) {
SendNotFoundResponse()
}
customer.delete();
response.status = 204
render ""
}



Follow on Twitter