Last Update: 01.08.2017. By Jens in Spring Boot | Spring Data
There’s an easy way to build a REST API for your Spring Data repositories instantly. It doesn’t matter if you are using JPA, MongoDB or any of the other stores available with Spring Data. Spring Data REST is the little helper. In this tutorial, we will take a closer look at how you can use it.
I assume you are using Spring Boot anyways, so the first step is to add the starter for Spring Data REST.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
The incredible magic of auto configuration will scan now your code for all your Repository interfaces and directly publish them as a REST API.
Spring Data REST is using the HATEOAS (Hypermedia As The Engine Of Application State) principle and supports HAL (Hypertext Application Language) as a semantic layer for metadata (like linking) on top of it.
HATEOAS is, for some, the holy grail for REST API and anything not using it, shall not call it self a RESTful API. Regardless of how you see it, it is the way of Spring Data REST.
The principle of HATEOAS is that each resource has its own URI (aka endpoint), and you always transfer the whole state of your object behind an endpoint; if you want to change it, manipulate it in your client and send it back to the server. It is commonly used with HTTP, but that’s not a requirement for using HATEOAS; it could also be used with messaging, etc. However, here it is exposed on HTTP.
The other important idea behind this principle is that clients shall only know one entry point to your API and can discover the API without any out of bound information like documentation in a wiki or word file.
As HATEOAS itself has no rules of how things like linking between resources, pagination, searches and other various meta data related task are handled, a few rivaling solutions exist, and HAL is for Spring Data REST HAL the winner.
When you start using Spring Data REST, you should be aware of these concepts and if it is a good solution for your particular context. However, discussing that is not part of this tutorial.
By default, the API is exposed at /, and you can start to access it. For making it a bit more visual, we create a simple sample application.
The sample application consists of a simple model of a user with some attributes and another one representing an address.
We use Spring Data JPA for it with an H2 database.
You can find the working sample on GitHub.
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String firstname;
private String lastname;
@ManyToOne(cascade= {CascadeType.ALL})
@JoinColumn(name="address_id")
private Address homeAddress;
//getter and setter omitted
}
The address:
@Entity
public class Address {
@Id
@GeneratedValue
private Long id;
private String street;
private String zipCode;
private String city;
private String country;
//getter and setter omitted
}
And our UserRepository looks like:
public interface UserRepository extends CrudRepository<User, Long>{
}
Last, but not least, the Spring Boot application:
@SpringBootApplication
public class SpringDataRestTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataRestTutorialApplication.class, args);
}
}
When we start SpringDataRestTutorialApplication now and load http://localhost:8080/ you will get a response like:
{
"_links" : {
"users" : {
"href" : "http://localhost:8080/users"
},
"profile" : {
"href" : "http://localhost:8080/profile"
}
}
}
__links_ is part of HAL and give an overview which links are available on exactly this endpoint.
users is the one Spring Data REST created for our user repository.
profile exposes additional meta data a potential client could use. We will ignore the profile and discover what users has to offer.
When you access http://localhost:8080/users now in your browser you will receive a response like:
{
"_embedded" : {
"users" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/users"
},
"profile" : {
"href" : "http://localhost:8080/profile/users"
}
}
}
It looks a bit like the one before, but __embedded_ is new which would contain a list of users if we had already some stored in the database.
So let’s add one.
To add a new user, we must make a POST requests to the users endpoint and post the new user as JSON.
{
"id": 1,
"firstname": "Horst",
"username": "horstm",
"lastname": "Mustermann",
"homeAddress": {
"street": "Zeil 1",
"city": "Frankfurt am Main",
"zipCode": "60313",
"country": "Germany"
}
}
On success, you will get a 201 response and the new user as a JSON. This JSON does also contain the links metadata and a special one indicating the URI for this user.
"_links": {
"self": {
"href": "http://localhost:8080/users/1"
... omitted
When you call the users endpoint again, the new user is included.
When you want to change a user you have two options.
Both requests are sent to the endpoint of the individual resource. So, if you want to update the user above, we must make the requests to http://localhost:8080/users/1. If everything went fine, we get a response with the new user’s state and an HTPP status of 200.
Make a PUT to http://localhost:8080/users/1 using the user JSON from above and change a single field. I changed the username.
Make a PATCH to http://localhost:8080/users/1 using the user JSON from above and include only the fields you want to change. I changed the street in the address.
Make a DELETE to http://localhost:8080/users/1 with no content and the user will be deleted. On success, it returns a 204 - no content.
If you reload the user list again, the record is gone.
When we use a PagingAndSortingRepository the collections endpoint for our model will also support pagination and sorting out of the box.
Change the UserRepository to:
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
}
When you call http://localhost:8080/users now, a new attribute is included in the JSON:
"page": {
"size": 1,
"totalElements": 2,
"totalPages": 2,
"number": 0
}
These are the standard values for Springs pagination, so I don’t think we need to cover them here.
In the links section of the response are also a few new links for navigation:
"first": {
"href": "http://localhost:8080/users?page=0&size=1"
},
"self": {
"href": "http://localhost:8080/users{?page,size,sort}",
"templated": true
},
"next": {
"href": "http://localhost:8080/users?page=1&size=1"
},
"last": {
"href": "http://localhost:8080/users?page=1&size=1"
},
first, next (or prev) and last are pretty self-explanatory. Interesting is that the self URI changed too. It now included three optional parameters, page for the page to retrieve, size for the number of results per page and sort for sorting. The sorting parameter is in the form property follow by a , and asc or desc. So, for sorting by street use &sort=homeAddress.street,desc.
Right now, we are using the basic CRUD operations and e.g., finding a specific user would be tedious by navigating through the collection endpoint. However, we can add it.
For that, we just add a new method to the repository like:
Page<User> findAllByUsername(@Param("username") String username, Pageable page);
When you access the user collection now, you will notice a new link named search.
"search": {
"href": "http://localhost:8080/users/search"
}
Following it, we will notice that our new repository method is exposed as:
"findAllByUsername": {
"href": "http://localhost:8080/users/search/findAllByUsername{?username,page,size,sort}",
"templated": true
},
Spring Data REST just takes our repository method and exposes it as a search for our model. It will also automatically map any @Param of the repository method to a query parameter we can use in the API.
A GET to http://localhost:8080/users/search/findAllByUsername?username=horstm will return all users with the username horstm.
It is cumbersome to test this by following the links manually. Luckily, there’s a browser for browsing HAL endpoints, and you can activate it by just adding the following dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
When you restart the application now and reload the root path, you will see the HAL browser.
The default behavior is helpful, but sometimes we want to take more control of how our data is handled.
First, you can take control of providing a RepositoryRestConfigurer and just adjust to your needs. Second, you can change some behavior with properties.
Certain configuration can be changed by setting properties. They are exposed under the prefix spring.data.rest.
For example:
You can find a complete list in the documentation.
A RepositoryDetectionStrategy is used to determine which repositories should be exposed in the API.
We will cover the annotations in the next section.
You can change the strategy by providing the RepositoryRestConfigurer in your @Configuration like:
@Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setRepositoryDetectionStrategy(
RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
}
};
}
Spring Data REST offers two annotations:
So, if we change the RepositoryDetectionStrategy to ANNOTATED like above, our UserRepository is only exposed when we add the @RepositoryRestResource annotation, and the exported flag on it is set to true (default).
You can use the @RepositoryRestResource also on the individual repository methods. For example, you could expose all methods by adding the annotation on the interface and disable only a single one by adding @RepositoryRestResource with exported = false to this particular method.
The commonly used parameters are:
The annotations allow a finer grained control of how your data is exposed.
Spring Data REST is an excellent solution for quickly exposing your Spring Data backend in an API. However, you need to be aware that your API is now following the HATEOAS principle and that this might not make sense for your particular situation. But this is a discussion for another article :-)