9.1.5.2 Implementing REST Controllers Step by Step - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.0.12
9.1.5.2 Implementing REST Controllers Step by Step
If you don't want to take advantage of the features provided by theRestfulController
super class, then you can implement each HTTP verb yourself manually. The first step is to create a controller:$ grails create-controller book
import grails.transaction.* import static org.springframework.http.HttpStatus.* import static org.springframework.http.HttpMethod.*@Transactional(readOnly = true) class BookController { … }
HTTP Method | URI | Controller Action |
---|---|---|
GET | /books | index |
GET | /books/${id} | show |
GET | /books/create | create |
GET | /books/${id}/edit | edit |
POST | /books | save |
PUT | /books/${id} | update |
DELETE | /books/${id} | delete |
The 'create' and 'edit' actions are already required if you plan to implement an HTML interface for the REST resource. They are there in order to render appropriate HTML forms to create and edit a resource. If this is not a requirement they can be discarded.The key to implementing REST actions is the respond method introduced in Grails 2.3. The
respond
method tries to produce the most appropriate response for the requested content type (JSON, XML, HTML etc.)Implementing the 'index' action
For example, to implement theindex
action, simply call the respond
method passing the list of objects to respond with:def index(Integer max) { params.max = Math.min(max ?: 10, 100) respond Book.list(params), model:[bookCount: Book.count()] }
model
argument of the respond
method to supply the total count. This is only required if you plan to support pagination via some user interface.The respond
method will, using Content Negotiation, attempt to reply with the most appropriate response given the content type requested by the client (via the ACCEPT header or file extension).If the content type is established to be HTML then a model will be produced such that the action above would be the equivalent of writing:def index(Integer max) { params.max = Math.min(max ?: 10, 100) [bookList: Book.list(params), bookCount: Book.count()] }
index.gsp
file you can render an appropriate view for the given model. If the content type is something other than HTML then the respond
method will attempt to lookup an appropriate grails.rest.render.Renderer
instance that is capable of rendering the passed object. This is done by inspecting the grails.rest.render.RendererRegistry
.By default there are already renderers configured for JSON and XML, to find out how to register a custom renderer see the section on "Customizing Response Rendering".Implementing the 'show' action
Theshow
action, which is used to display and individual resource by id, can be implemented in one line of Groovy code (excluding the method signature):def show(Book book) { respond book }
id
parameter of the request. If the domain instance doesn't exist, then null
will be passed into the action. The respond
method will return a 404 error if null is passed otherwise once again it will attempt to render an appropriate response. If the format is HTML then an appropriate model will produced. The following action is functionally equivalent to the above action:def show(Book book) { if(book == null) { render status:404 } else { return [book: book] } }
Implementing the 'save' action
Thesave
action creates new resource representations. To start off, simply define an action that accepts a resource as the first argument and mark it as Transactional
with the grails.transaction.Transactional
transform:@Transactional def save(Book book) { … }
if(book.hasErrors()) { respond book.errors, view:'create' } else { … }
book.save flush:true withFormat { html { flash.message = message(code: 'default.created.message', args: [message(code: 'book.label', default: 'Book'), book.id]) redirect book } '*' { render status: CREATED } }
Implementing the 'update' action
Theupdate
action updates an existing resource representations and is largely similar to the save
action. First define the method signature:@Transactional def update(Book book) { … }
if(book == null) { render status: NOT_FOUND } else { … }
if(book.hasErrors()) { respond book.errors, view:'edit' } else { … }
book.save flush:true withFormat { html { flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), book.id]) redirect book } '*' { render status: OK } }
Implementing the 'delete' action
Thedelete
action deletes an existing resource. The implementation is largely similar to the update
action, expect the delete()
method is called instead:book.delete flush:true withFormat { html { flash.message = message(code: 'default.deleted.message', args: [message(code: 'Book.label', default: 'Book'), book.id]) redirect action:"index", method:"GET" } '*'{ render status: NO_CONTENT } }
index
action, whilst for other content types a response code 204 (NO_CONTENT) is returned.