I want to use the HAL format for my REST API to include embedded resources. I'm using Spring HATEOAS for my APIs and Spring HATEOAS seems to support embedded resources; however, there's no documentation or example on how to use this.
Can someone provide an example how to use Spring HATEOAS to include embedded resources?
Make sure to read Spring's documentation about HATEOAS, it helps to get the basics.
In this answer a core developer points out the concept of Resource
, Resources
and PagedResources
, something essential which is is not covered by the documentation.
It took me some time to understand how it works, so let's step through some examples to make it crystal-clear.
the resource
import org.springframework.hateoas.ResourceSupport;
public class ProductResource extends ResourceSupport{
final String name;
public ProductResource(String name) {
this.name = name;
}
}
the controller
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("products/{id}", method = RequestMethod.GET)
ResponseEntity<Resource<ProductResource>> get(@PathVariable Long id) {
ProductResource productResource = new ProductResource("Apfelstrudel");
Resource<ProductResource> resource = new Resource<>(productResource, new Link("http://example.com/products/1"));
return ResponseEntity.ok(resource);
}
}
the response
{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}
}
Spring HATEOAS comes with embedded support, which is used by Resources
to reflect a response with multiple resources.
@RequestMapping("products/", method = RequestMethod.GET)
ResponseEntity<Resources<Resource<ProductResource>>> getAll() {
ProductResource p1 = new ProductResource("Apfelstrudel");
ProductResource p2 = new ProductResource("Schnitzel");
Resource<ProductResource> r1 = new Resource<>(p1, new Link("http://example.com/products/1"));
Resource<ProductResource> r2 = new Resource<>(p2, new Link("http://example.com/products/2"));
Link link = new Link("http://example.com/products/");
Resources<Resource<ProductResource>> resources = new Resources<>(Arrays.asList(r1, r2), link);
return ResponseEntity.ok(resources);
}
the response
{
"_links": {
"self": { "href": "http://example.com/products/" }
},
"_embedded": {
"productResources": [{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}, {
"name": "Schnitzel",
"_links": {
"self": { "href": "http://example.com/products/2" }
}
}]
}
}
If you want to change the key productResources
you need to annotate your resource:
@Relation(collectionRelation = "items")
class ProductResource ...
This is when you need to start to pimp Spring. The HALResource
introduced by @chris-damour in another answer suits perfectly.
public class OrderResource extends HalResource {
final float totalPrice;
public OrderResource(float totalPrice) {
this.totalPrice = totalPrice;
}
}
the controller
@RequestMapping(name = "orders/{id}", method = RequestMethod.GET)
ResponseEntity<OrderResource> getOrder(@PathVariable Long id) {
ProductResource p1 = new ProductResource("Apfelstrudel");
ProductResource p2 = new ProductResource("Schnitzel");
Resource<ProductResource> r1 = new Resource<>(p1, new Link("http://example.com/products/1"));
Resource<ProductResource> r2 = new Resource<>(p2, new Link("http://example.com/products/2"));
Link link = new Link("http://example.com/order/1/products/");
OrderResource resource = new OrderResource(12.34f);
resource.add(new Link("http://example.com/orders/1"));
resource.embed("products", new Resources<>(Arrays.asList(r1, r2), link));
return ResponseEntity.ok(resource);
}
the response
{
"_links": {
"self": { "href": "http://example.com/products/1" }
},
"totalPrice": 12.34,
"_embedded": {
"products": {
"_links": {
"self": { "href": "http://example.com/orders/1/products/" }
},
"_embedded": {
"items": [{
"name": "Apfelstrudel",
"_links": {
"self": { "href": "http://example.com/products/1" }
}, {
"name": "Schnitzel",
"_links": {
"self": { "href": "http://example.com/products/2" }
}
}]
}
}
}
}