Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Introduction to HATEOAS With Spring Boot Data Rest

DZone's Guide to

Introduction to HATEOAS With Spring Boot Data Rest

Learn how to implement Spring Data Rest for APIs to analyze domain models and expose Hypermedia Driven REST endpoints automatically.

· Integration Zone
Free Resource

Modernize your application architectures with microservices and APIs with best practices from this free virtual summit series. Brought to you in partnership with CA Technologies.

HATEOAS (Hypermedia as the Engine of Application State) specifies that REST APIs should provide enough information to the client to interact with the server. This is different from 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.

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 to different databases like MongoDB, MySQL etc. The full list of supported databases is given here.

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 {
    @GeneratedValue
    @Id 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.

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"
        }
    }
}


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.

The Integration Zone is proudly sponsored by CA Technologies. Learn from expert microservices and API presentations at the Modernizing Application Architectures Virtual Summit Series.

Topics:
java ,spring data rest ,hateoas ,spring data ,jpa ,integration

Published at DZone with permission of Mohit Sinha, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}