11

Official Grails documentation says that

Version 2.0.x of the scaffolding plugin includes different scaffolding templates that are aligned with the new REST APIs introcued in Grails 2.3 and above. (taken from here http://grails.org/doc/latest/guide/scaffolding.html)

But I can't make (or I don't understand the concept) work RESTfulness together with scaffolding.

Let's start from scratch:

grails create-app myapp
cd myapp/
grails create-domain-class Book
grails create-scaffold-controller myapp.Book

Add a field to the domain class

class Book {
    String text

    static constraints = {
    }
}

and run the app with grails run-app. Surfing on the http://localhost:8080/myapp/ shows that scaffolding works great:

  • http://localhost:8080/myapp/book/index page shows books list
  • http://localhost:8080/myapp/book/show/1 page show details for the book with id = 1
  • http://localhost:8080/myapp/book/create page creates a book
  • and so force, good old scaffolding.

Let's see what about REST. Official docs say I should use URLs like http://localhost:8080/myapp/books/... for the REST but any attempt to access the app, like this curl -i -H "Accept: application/json" localhost:8080/myapp/books/1 returns 404 with bunch of HTML.

Ok, let's read docs carefully:

The easiest way to create a RESTful API in Grails is to expose a domain class as a REST resource. This can be done by adding the grails.rest.Resource transformation to any domain class

No problem, now the Book class heading is

import grails.rest.*

@Resource(uri='/books') class Book {

Now surfing on the http://localhost:8080/myapp/ shows that scaffolding is broken:

  • http://localhost:8080/myapp/book/index page shows books list
  • http://localhost:8080/myapp/book/create page shows xml output <?xml version="1.0" encoding="UTF-8"?><book><text /></book>
  • and so force, bad new xml output.

I'd played with @Resource and "/books"(resources:"book") in URLMappings.groovy but hadn't found any working solution which makes possible scaffolding and RESTfulness work back-to-back. Indeed, I managed to make them work separately.

Update

I'd found the way how to achieve the desired goal. The way I found is:

  1. Mark the Book class with @Resource(uri = "/books").
  2. Remove scaffold controller BookController.
  3. Create dedicated controller with scaffolding for the Book: class HumanBookController {static scaffold = Book}

Now scaffold GUI pages with URLs like http://localhost:8080/myapp/humanBook/index work pretty well. Either json requests are handled well with URLs like http://localhost:8080/myapp/books/1. But it's not elegant to have 2 controllers doing same things for common web and json.

aka_sh
  • 549
  • 1
  • 7
  • 18
  • 2
    Try adding `"/books"(resources:"book")` into your urlmapping.groovy instead of @Resource on your domain see if that helps, You can use `url-mappings-report` to see which services are exposed. – Alidad Oct 19 '13 at 14:06
  • Alidad, I'd tried it - and it didn't work (returns 404 for valid requests). I guess, only `@Resource` generates needed controller in runtime. Moreover, the docs say that RESTy URL mapping in UrlMapping should be used in conjunction with `@Resource` - it's just another place to define mappings with some additional (like nest resources) features. – aka_sh Oct 19 '13 at 21:37
  • 1
    I'm currently (using 2.4.0) having the same issue, it's like the mappings are all wrong. :( – genuinefafa Jun 01 '14 at 01:38
  • The solution did not work for me with 2.4.4. If I remove static scaffold = true from the controller, the REST URLS all still give 404. Basically, @Resource seems to do nothing. – John Little Nov 03 '14 at 13:14
  • As per the post below by evanwong, this is working for me in 2.4.4 without the @Resource in the domain class but this in UrlMappings.groovy: "/books"(resources:"Book"). I have REST and scaffolded screens working together as desired. Also a big thanks to Alidad for the tip on the grails url-mappings-report command. – ed209 Jan 09 '15 at 16:16

6 Answers6

15

You can do this:

import grails.rest.RestfulController

class BookController extends RestfulController {

    static responseFormats = ['html', 'json']

    BookController() {
        super(Book)
    }
}

And then in the UrlMappings.groovy:

 "/books"(resources:"book")
 "/$controller/$action?/$id?(.${format})?"{
    constraints {
        // apply constraints here
    }
  }

No need to add @Resource in the domain. You can now have /books/1.json or /books/1.html to point to the right places. You might still need to do grails generate-view Book to have the view generated. But although you need to generate the views for html, you keep only single controller and path.

evanwong
  • 5,054
  • 3
  • 31
  • 44
  • 1
    evanwong, looks like your solution really works. I checked it on my Grails 2.3.1 and: 1. yes, I had to generated view for the Book, 2. I had some concerns regarding to use `responseFormats` cause I thought it would break pretty urls (a need of ".html" for the human urls) but it appears that regular urls (without ".html") are working just fine. I agree with you that having 1 controller with generated scaffold view (your solution) is better than having 2 controllers (mine) but still it's not what the Grails docs are promising. – aka_sh Jan 07 '14 at 06:10
1

I had the same problems as yours. That might be a trivial solution and not for every case, but try updating your Grails version. As for me: Grails 2.3.4 -> Grails 2.3.6 worked. Hope that help anyone.

Alexander Davliatov
  • 723
  • 2
  • 8
  • 13
1

I'm currently using Grails 2.4.0, the solution came by doing this:

  • Controller: BookController { static scaffold = true }
  • Domain: Book { .... } // WITHOUT @Resource

The result is that you can:

  • /book.json to get a list JSONized
  • /book/index to get an HTML standard scaffolding
  • /book/create html scaffold for a new item
  • /book/show/1 html scaffold edit item 1
  • /book/show/1.json JSON for item id: 1

I'ts wicked, i know. I found this and it get me going.

genuinefafa
  • 703
  • 1
  • 6
  • 21
1

With Grails 2.4.4 I was able to get the scaffolding working with single controller using the following steps:

  1. Added a URL to Resource mapping in UrlMappings.groovy, e.g. "/books"(resources:"book")
  2. Inserted static scaffold = true into the generated controller

I did not verify if the following made a difference, but I also set grails.mime.disable.accept.header.userAgents = [] and grails.mime.use.accept.header = true in Config.groovy (the latter is presumably the new default value).

Both of the scaffolded REST and UI interfaces are working fine with the following tests:

  • GET /app//1 (passing Accept header)
  • GET /app//1.json (no Accept header)
  • POST /app/ (with payload as json or form encoded)
  • DELETE /app//1
  • PUT /app//1 (with json payload. form payload updated the object, but sent back 302 redirects)

EDIT

  1. Removed the Resource annotation step and clarified the URL mapping setup
  2. The URI assigned in the URL mapping is not the same as the default URI for the controller. For example, "books" instead of "book". After adding this mapping, the URI for the controller will default to the URI in UrlMapping, but the original URI will continue to work.
dachiz
  • 521
  • 1
  • 4
  • 6
0

The generated controller is a Restful controller because it implements actions aware of requests like:

curl -i -X GET yourDomain:8080/yourApp/books.json

It returns a list of books in json format. (10 books, assuming that you created test data, did you?)

You can append a parameter like:

curl -i -X GET yourDomain:8080/yourApp/books.xml?40

By default you will get the html format. You need to append .json or .xml to get the correct data.

You can to use the Accept header too

curl -i -X GET -H "Accept: application/xml" yourDomain/books/1

returns details of book with id=1 in xml format. Finally

curl -i -X POST -H "Content-Type: application/json" -d "{name: 'Book'}" yourDomain/books

creates a new book and

curl -i -X PUT -H "Content-Type: application/json" -d "{name: 'Book'}" yourDomain/books/1

updates name of book with id=1

All resources need to be exposes through and url. The url is not generated for you, you should write it on UrlMappings file:

"/v1/books"(resources: "book")

Where the first string "/v1/books" is the uri and the second string "book" is the controller name following the grails convention. (The preceding v1 string is because I always put version number to my API URIs)

 | GET    | /v1/books            | Action: index  |
 | GET    | /v1/books/create     | Action: create |
 | POST   | /v1/books            | Action: save   |
 | GET    | /v1/books/${id}      | Action: show   |
 | GET    | /v1/books/${id}/edit | Action: edit   |
 | PUT    | /v1/books/${id}      | Action: update |
 | DELETE | /v1/books/${id}      | Action: delete |
AA.
  • 4,496
  • 31
  • 35
  • you refer to a 'generated' controller. Can this be done with one 'scaffold'ed controller? – aldrin Dec 07 '13 at 04:48
  • AA., sorry for huge delay in reply. I just tested what you proposed and it doesn't work on my side: `curl -i -X GET localhost:8080/myapp/books.json` results in `HTTP/1.1 404 Not Found` with bunch of HTML. `curl -i -X GET -H "Accept: application/xml" localhost:8080/books/1` brings simple 404 response. – aka_sh Jan 07 '14 at 05:42
  • according to the 2.3.4 docs, all that is required is to add @Resource with a uri mapping to the domain class. That should generate the controller and do all the URL mappings. So far I can't get it to work using that method. http://grails.org/doc/latest/guide/webServices.html – Micho Rizo Feb 07 '14 at 20:33
  • I have the same issue. @Resource does not work at all (get 404 for any of the documented calls, i.e. none of the above URL examples work) – John Little Nov 03 '14 at 12:40
0

All that should be required is @Resource annotation with the uri on the Domain class. If you want specific formats (default format is first), also include the formats:

@Resource(uri='/books', formats=['json', 'xml'])

That should be it. If ypu are still having trouble finding your dynamic @Resource endpoint, try running:

grails url-mappings-report

That will give you a nice summary of all urls, including those backed by scaffolded controllers for @Resource domains. I have found that I tend to make silly mistakes when trying to "guess" the URL - using the report output ensures you and grails are in agreement.

durp
  • 198
  • 1
  • 6