Expose Your Spring Data Repositories as a REST API

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.

Getting Started and Basics

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

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.

Working with the Model Endpoint(s)

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.

Add a new User

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

spring-data-rest-tutorial-post.png

When you call the users endpoint again, the new user is included.

Modifying a User

When you want to change a user you have two options.

  1. Update the whole user with a PUT request and sending all fields in the JSON
  2. A partial update with a PATCH request and only sending the changed fields

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.

Full Update

Make a PUT to http://localhost:8080/users/1 using the user JSON from above and change a single field. I changed the username.

spring-data-rest-tutorial-put.png

Partial Update

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.

spring-data-rest-tutorial-patch.png

Delete a User

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.

Pagination

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.

Searching

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.

HAL Browser

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.

hal-browser.png

Advanced Configuration

The default behavior is helpful, but sometimes we want to take more control of how our data is handled.

Global Configuration

First, you can take control of providing a RepositoryRestConfigurer and just adjust to your needs. Second, you can change some behavior with properties.

Properties

Certain configuration can be changed by setting properties. They are exposed under the prefix spring.data.rest.

For example:

  • default-page-size: set the default number of items per collection page
  • basePath: the base path of the exposed API

You can find a complete list in the documentation.

RepositoryDetectionStrategy

A RepositoryDetectionStrategy is used to determine which repositories should be exposed in the API.

  • DEFAULT: all public repository interfaces but considers settings on annotations
  • ALL: all repositories independently of type visibility and annotations
  • ANNOTATION: only annotated repositories unless they are set to false
  • VISIBILITY: only public annotated repositories

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);
        }
    };
}

Annotations

Spring Data REST offers two annotations:

  • @RestResource is used on model classes and define how to expose the model
  • @RepositoryRestResource is used on repositories (or repo methods) and define how to expose the model

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:

  • exported: declares if the method or repository is exposed
  • path: the path for the model

The annotations allow a finer grained control of how your data is exposed.

Conclusion

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 :-)