1. Overview
HATEOAS (Hypermedia as the Engine of Application State) specifies that the REST API’s should provide enough information to the client to interact with the server. This is different from the SOA (Service-Oriented Architecture) where a client and a server interact through a fixed contract. We’ll look more into HATEOAS in a while.
Spring Data Rest is built on top of Spring Data, Spring Web MVC & Spring Hateos. It analyzes all the domain models and exposes Hypermedia Driven REST endpoints for them automatically. In the meanwhile, all the features of Spring Data Repositories like sorting, pagination etc. are available in these endpoints.
We’ll see with the help of a very simple example how to implement this.
2. Dependencies
We’ll use Gradle to build our project.
dependencies { | |
compile("org.springframework.boot:spring-boot-starter-data-rest") | |
compile 'org.springframework.boot:spring-boot-starter-data-jpa' | |
compile("com.h2database:h2") | |
compileOnly('org.projectlombok:lombok') | |
testCompile('org.springframework.boot:spring-boot-starter-test') | |
} |
We’ll use H2 to run our project. The same concept can be applied for different databases like MongoDB, MySQL etc. The full list of supported databases is given here.
3. Spring Data Rest
In this example, we’ll use JPA to create cities and countries.
Let’s have a look at our Country class.
@Data | |
@Entity | |
@RestResource | |
@NoArgsConstructor | |
@FieldDefaults(level = AccessLevel.PRIVATE) | |
public class Country { | |
@Id | |
@GeneratedValue | |
Long id; | |
String name; | |
} |
Let’s have a look at our City class.
@Data | |
@Entity | |
@NoArgsConstructor | |
@RestResource | |
@FieldDefaults(level = AccessLevel.PRIVATE) | |
public class City { | |
@GeneratedValue | |
@Id Long id; | |
String name; | |
@ManyToOne Country country; | |
} |
As we are using JPA in our project, we are creating an association between a City and a Country. Many Cities can be associated with a Country.
Let’s create the repositories for them.
public interface CountryRepository extends JpaRepository<Country, Long> { | |
} |
This will create a repository for Country and also expose the REST endpoints (GET, POST, PUT, DELETE, PATCH) for the same.
As JPARepository
extends PagingAndSortingRepository
, paging & sorting functionality will be automatically added for the GET endpoint.
By default, the path is derived from the uncapitalized, pluralized, simple class name of the domain class being managed.
In our case, the path will be countries.
@RepositoryRestResource(path = "metropolises") | |
public interface CityRepository extends JpaRepository<City, Long> { | |
} |
We have customized the path to metropolises.
4. HATEOAS
Let’s check the APIs after we run our project.
curl 'http://localhost:8080'
{ | |
"_links": { | |
"countries": { | |
"href": "http://localhost:8080/countries{?page,size,sort}", | |
"templated": true | |
}, | |
"cities": { | |
"href": "http://localhost:8080/metropolises{?page,size,sort}", | |
"templated": true | |
}, | |
"profile": { | |
"href": "http://localhost:8080/profile" | |
} | |
} | |
} |
We get some information about the available APIs. We can further explore about the metadata by hitting the profile API. You can read more about the metadata here.
Let’s add a few Countries.
curl 'http://localhost:8080/countries' -X POST -d '{"name":"Japan"}' -H 'Content-Type: application/json' | |
curl 'http://localhost:8080/countries' -X POST -d '{"name":"India"}' -H 'Content-Type: application/json' | |
curl 'http://localhost:8080/countries' -X POST -d '{"name":"Germany"}' -H 'Content-Type: application/json' | |
curl 'http://localhost:8080/countries' -X POST -d '{"name":"Canada"}' -H 'Content-Type: application/json' | |
curl 'http://localhost:8080/countries' -X POST -d '{"name":"Australia"}' -H 'Content-Type: application/json' |
Let’s fetch a paginated result of Countries with the results sorted by Country name, the page size 2 and the 1st page.
curl 'http://localhost:8080/countries/?sort=name,asc&page=1&size=2'
{ | |
"_embedded": { | |
"countries": [ | |
{ | |
"name": "Germany", | |
"_links": { | |
"self": { | |
"href": "http://localhost:8080/countries/3" | |
}, | |
"country": { | |
"href": "http://localhost:8080/countries/3" | |
} | |
} | |
}, | |
{ | |
"name": "India", | |
"_links": { | |
"self": { | |
"href": "http://localhost:8080/countries/2" | |
}, | |
"country": { | |
"href": "http://localhost:8080/countries/2" | |
} | |
} | |
} | |
] | |
}, | |
"_links": { | |
"first": { | |
"href": "http://localhost:8080/countries?page=0&size=2&sort=name,asc" | |
}, | |
"prev": { | |
"href": "http://localhost:8080/countries?page=0&size=2&sort=name,asc" | |
}, | |
"self": { | |
"href": "http://localhost:8080/countries" | |
}, | |
"next": { | |
"href": "http://localhost:8080/countries?page=2&size=2&sort=name,asc" | |
}, | |
"last": { | |
"href": "http://localhost:8080/countries?page=2&size=2&sort=name,asc" | |
}, | |
"profile": { | |
"href": "http://localhost:8080/profile/countries" | |
} | |
}, | |
"page": { | |
"size": 2, | |
"totalElements": 5, | |
"totalPages": 3, | |
"number": 1 | |
} | |
} |
Apart from the expected countries, we also get the links to different pages and further information that might help in handling pagination better.
The links to the first, previous, self, next and last pages can directly be used.
Let’s add a City and associate it with a Country.
curl 'http://localhost:8080/metropolises' -X POST -d '{"name":"Osaka", "country":"http://localhost:8080/countries/1"}' -H 'Content-Type: application/json'
We have to pass the url of the country and this will be mapped to Japan. We saved Japan first, hence its id is 1.
Let’s see what we get when we fetch that City.
curl 'http://localhost:8080/metropolises/1'
{ | |
"name": "Osaka", | |
"_links": { | |
"self": { | |
"href": "http://localhost:8080/metropolises/1" | |
}, | |
"city": { | |
"href": "http://localhost:8080/metropolises/1" | |
}, | |
"country": { | |
"href": "http://localhost:8080/metropolises/1/country" | |
} | |
} | |
} |
We are getting a link to the Country associated with it. Let’s see what we get in response for it.
{ | |
"name": "Japan", | |
"_links": { | |
"self": { | |
"href": "http://localhost:8080/countries/1" | |
}, | |
"country": { | |
"href": "http://localhost:8080/countries/1" | |
} | |
} | |
} |
5. Conclusion
I have tried explaining, with a simple example, how to create REST applications using Spring Data Rest. You can read more about setting up policies and integrating with Spring Security here.
You can find the complete example on Github.