WARREN-R
WARREN-R

Reputation: 111

Spring MVC, Thymeleaf & REST

I currently have a project using a Spring controller and Thymeleaf to create a little browser app. The controller class is declared as

@Controller public class MyController {

Inside the controller I have a GET defined as

@RequestMapping(value = "/foobars", method = RequestMethod.GET)
String index(Model model, --- more params ---) {
    //call repository and get foobarsList
    //model.addAttribute("foobars", foobarsList);
          ...
    return "foobars/foobarThymeleafTemplate"
}

The call repository and get foobarList is a call to a MongoRepository defined as:

public interface FoobarRepository extends MongoRepository< ... cut for brevity> {

    @RestResource(rel = "by-id") 
    Marker findMarkerById(String id);

    ... additional @RestResources cut for brevity ...
}

Again, the Browser App looks great. The GET calls the repository, populates the model with the list of foobars and Thymeleaf does it thing with that list.

PROBLEM: Now I need to access that same data from an Android App and I would prefer to use REST and just consume JSON in the Android App. I want to keep Thymeleaf but will refactor the browser app if necessary.

QUESTION: Is there a way to somehow use the same @Controller or will I have to maintain a second FoobarRestController using @RestController with /restFoobars endpoints? The second REST controller works for sure but it seems kind of sloppy ... poor design.

Your thoughts and recommendations?

Thanks again. -Rich

Upvotes: 10

Views: 17030

Answers (3)

Guy Pardon
Guy Pardon

Reputation: 494

You can use @RestController for both Thymeleaf HTML and REST/JSON it seems.

The following works for me:

@RestController
@RequestMapping("/projects")
public class ProjectController {

        /**
         * A REST reponse (JSON)
         * 
         * @param name
         * @return
         */
        @GetMapping(value = "/{name}")
        public Project get(@PathVariable String name) {
            return new Project(1l, name); 
        }

        /**
         * A thymeleaf HTML response
         */
        @GetMapping(value = "/hello.html")
        public String getAsHtml() {
            return "hello"; //name of the html file in src/main/resources/templates
        }
}

Upvotes: 0

Faraj Farook
Faraj Farook

Reputation: 14855

Expose Crud

Use the @Controller to do the work of handling HTML page and using the Repository you can expose the entity via rest API for the basic actions like crud by using SPRING-DATA-REST to expose your entities. I think you are already doing it by looking at the code

 @RestResource(rel = "by-id") 
    Marker findMarkerById(String id);

Expose Business logic

If you want to expose any business logic, you have to create a Service layer and simply call it via your @Controller for the web page. and create another controller as @RestController for the web API interface.

You may note, you are not duplicating any code here, as the logic is written at the single point in the Service layer. But using different controllers for different purposes.

As you don't need all the logic in web page to get exposed to API, use of separate controller for REST may be a clean design implementation for your code if you can namespace your code as app.web and app.api.

Food for thought

Use a complete REST API implementation for web pages and android. Then use AngualarJS or backboneJs to do the client side implementation with HTML5. I think this is the future.

Upvotes: 2

My preferred approach here is to use inheritance:

@RequestMapping('/foobars')
abstract class FoobarBaseController {

    @RequestMapping
    abstract listAll()
}

@Controller
class FoobarHtmlController extends FoobarBaseController {
    @Override ModelAndView listAll() {
        new ModelAndView('foobars/foobarThymeleafTemplate', [foobars: foobarsList])
    }
}

@RestController
@RequestMapping('/foobars', produces = MediaType.APPLICATION_JSON_VALUE)
class FoobarJsonController extends FoobarBaseController {
    @Override Collection<Foobar> listAll() {
        foobarsList
    }
}

Alternatively, if there's significant work to be done checking inputs or the like, you can implement that in the BaseController and have an abstract listAllResponse(DomainObject foo) that then returns the appropriate ModelAndView (HTML) or DTO (JSON).

The one pitfall of this approach is that you can't override just part of the @RequestMapping, so you do have to repeat the class's part of the mapping when you specify the produces parameter, but you can inherit the method-level mappings with no problem.

Upvotes: 2

Related Questions