TL;DR: In this article, you will learn how to develop REST APIs with ease by using Spring Data REST and Spring Boot together. Throughout the article, you will scaffold a new Spring Boot application, create a JPA entity, and use Spring Data REST to provide some basic operations over it. Besides that, you will also learn how to validate the data your API is dealing with and how to secure the application. If you need, you can check the final code developed throughout the article in this GitHub repository.
“Learn how to develop REST APIs with ease by using Spring Data REST and Spring Boot together.”
Tweet This
Introduction
Developing RESTful APIs is a task that most developers have faced at least once in their lives. Nowadays, with the increasing popularity of front-end frameworks like Angular, React, and Vue.js and with the mass adoption of smartphones, RESTful APIs became the most popular approach backend developers leverage to provide an interface for their applications. Knowing that, the Spring community, as you will see in this article, worked hard to build Spring Data REST, a framework that facilitates the life of developers while creating these APIs.
Spring Data REST builds on top of Spring Data repositories, analyzes your application’s domain model and exposes hypermedia-driven HTTP resources for aggregates contained in the model. — Spring Data REST
Together with Spring Boot, Spring Data REST enables developers to create production-ready APIs in a matter of minutes. Want some proof? Keep reading!
Note: If you have never used Spring to develop any application, you can still follow along with this article. However, if you don't have previous experience with Java or have never even heard about JPA (Java Persistence API), you might find things here a bit magical. Either way, don't worry, you can follow this article along, learn how to develop RESTful APIs with Spring Boot and Spring Data REST and, after that, learn about the new terms you hear along the way.
What You Will Build
To learn about Spring Data REST, you will build a simple RESTful API that exposes CRUD (Create, Retrieve, Update, and Delete) operations over an entity called
Ad
. The Ad
entity, in this case, stands for advertisements and will be used to represent some product or service that a user is trying to sell. As such, your API will enable users to manipulate ads that hold information like title, owner, description, and price.As mentioned before, while developing this RESTful API, you will learn how to:
- provide an API with different basic operations over the entity created (
);Ad
- validate data;
- and secure the API.
Prerequisites
To follow along with this article, you will need Java 8 or greater (up to Java 11) installed in your machine. If you don't have Java yet, please, refer to the official documentation to install it in your development environment.
Besides Java, you will need some IDE (Integrated Development Environment) to help you in the development process. For that, there are a few great choices available out there (e.g., IntelliJ IDEA, Eclipse, and NetBeans). If you don't have a preferred one yet, try using the community version of IntelliJ IDEA.
Scaffolding an App with Spring Boot
For starters, before diving into developing your RESTful API, you will need to scaffold your new application. To do so, open the Spring Initializr website in your browser and fill the presented form as follows:
- Generate a Gradle Project with Java and Spring Boot
(the author used2.X
while writing this article).2.1.3
- Group:
.com.auth0
- Artifact:
.ads
Then, on the Dependencies section, use the search for dependencies field to add five dependencies:
- Web: A library that helps you develop web applications with Tomcat and Spring MVC.
- Rest Repositories: The library that will allow you to expose your database as a RESTful API.
- JPA: The Java Persistence API library that will help you map SQL databases to objects and vice-versa.
- Lombok: Java annotation library that helps you code faster by reducing boilerplate code.
- HSQLDB: An embedded SQL database that you will allow you to sping up an in-memory database instead of having to install and configure a new one.
After filling this page, click on the Generate Project button to download your new application. When done, you will have to extract the downloaded
.zip
file and move the extracted folder to a desired location.If you are on a Unix-like operating system, you can also unzip the downloaded file with the following command:
unzip ads.zip
With that in place, use your IDE to open your new project. For example, if you are using IntelliJ IDEA, you can open the project by issuing the following command from the project root:
idea .
Creating RESTful APIs for JPA Entities with Spring Data REST
Now that you have scaffolded a new Spring Boot project and that you have opened it in your IDE, the first thing you will do is to create the JPA entity that you will expose with the help of Spring Data REST. To do so, create a new class called
Ad
inside the com.auth0.ads
package, and add the following code to it:// ./src/main/java/com/auth0/ads/Ad.java package com.auth0.ads; import lombok.EqualsAndHashCode; import lombok.Getter; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.math.BigDecimal; @Entity @Getter @EqualsAndHashCode public class Ad { @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long id; public String owner; public String title; public String description; public BigDecimal price; protected Ad() { } public Ad(String owner, String title, String description, BigDecimal price) { this.owner = owner; this.title = title; this.description = description; this.price = price; } }
As you can see from the code above, you are defining a new JPA
@Entity
called Ad
to hold five different fields. Most of these fields are self-explanatory (their names should be enough for you to understand what they will hold). The only one that might need some explanation is the Long id
field. As you can see, this field is marked with two annotations:
: This annotation marks the field as the unique identifier of the ad (i.e., the primary key in the database).@Id
: This annotation tells JPA that the database will need to supply its value. In this case, the database will auto-generate (@GeneratedValue
) this field, no matter how (the strategy to do so depends on what database you are using).GenerationType.AUTO
After creating the
Ad
JPA entity, you will need to focus on creating a class that will allow you to interface with the database. Also, you will need to map the database operations into RESTful API endpoints so external clients can use them. Sounds like a lot of work? On the contrary! This is where Spring Data REST shines.To achieve both things described in the last paragraph (map to the database and expose the operations in your API), you will need to do only one thing. You will need to create an interface called
AdRepository
(inside the com.auth0.ads
package) and add the following code to it:// ./src/main/java/com/auth0/ads/AdRepository.java package com.auth0.ads; import org.springframework.data.repository.PagingAndSortingRepository; public interface AdRepository extends PagingAndSortingRepository<Ad, Long> { }
That's it! With this interface in place, you are ready to run your application and start issuing requests to it. Magic? Indeed it looks like so. However, what is happening here is that Spring Boot (a framework that is bound to the "convention over configuration" strategy) and Spring Data REST identify that you defined an interface that
extends PagingAndSortingRepository
and work together to create a bunch of endpoints for you.If you check the JavaDocs of the
PagingAndSortingRepository
interface, you will see that this interface is an "extension of CrudRepository
to provide additional methods to retrieve entities using pagination and sorting".More specifically, as described on the Core Concepts documentation of the Spring Data library, together,
PagingAndSortingRepository
and CrudRepository
add the following methods to your application to allow it to manipulate your entities in a SQL database:
: A method that enables the app to save (insert or update) entities.save(S entity)
: A method that enables the app to retrieve an entity based on its primary key.findOne(ID primaryKey)
: A method that enables the app to retrieve all entities saved on the database.findAll()
: A method that returns how many entities exist on the database.Long count()
: A method to remove a specific entity from the database.delete(T entity)
: A method to check if a particular entity, based on a primary key, exists on the database.exists(ID primaryKey)
: A method to return a list of all entities, sorted by some criteria, saved on the database.findAll(Sort sort)
: A method to return subsets (pages) of the entities saved on the database.findAll(Pageable pageable)
Then, as you are using Spring Data REST, this library creates RESTful endpoints to exposes all the methods defined by
PagingAndSortingRepository
and CrudRepository
.To see all this magic in action, you can either press the play button in your IDE, or you can issue the following code in a terminal (as long as this terminal is pointing to the project root):
./gradlew bootRun
After the application starts executing (it will take a few seconds for it to be ready), you can start sending HTTP requests to its endpoints. To do so, you can use a graphical HTTP client like Postman, or you can open a new terminal and use a tool like
curl
. The next subsection will show you how to use curl
to issue some requests to your new application. However, translating them to Postman or a similar client should not be a problem.Note: You will know that the app is running when you see a message similar to "Started AdsApplication in 8.904 seconds (JVM running for 9.361)". By the way, the message on the terminal will be showing
.75% EXECUTING
Issuing requests to the Spring Data REST endpoints
For starters, to confirm that your application is working as expected, you can issue the following request (you might need to run the command below in a new terminal or, as mentioned, you can use Postman):
# check if there are ads persisted curl http://localhost:8080/ads
Running this command will make your API return all the ads persisted on the database (which will be none:
"ads" : [ ]
), and a few other things like:
: the number of ads that the API will return on each page (page.size
).20
: the number of ads returned on this page (page.totalElements:
).0
: the number of pages available (page.totalPages:
).0
: the current page number (page.number:
).0
As you haven't created any entity yet, the results won't be very interesting. So, the next thing you can do is to issue a request to insert a new ad in your API:
# insert a new ad curl -X POST -H "Content-Type:application/json" -d '{ "owner": "me", "title": "My Car", "description": "Pretty awesome but, unfortunately, I have to sell it.", "price": 225599.99 }' http://localhost:8080/ads
Issuing this command will output the details of the ad created (all the properties above) along with a link to the ad itself (
self.href
). If you use this link in a curl
command, you will retrieve the details of the ad:# retrieve the new ad curl http://localhost:8080/ads/1
Now, if you run the command to see the list of persisted ads again:
curl http://localhost:8080/ads
You will see that the
ads
array includes your new ad and that the page
properties (size
, totalElements
, etc.) are updated accordingly.After that, if you want, you can delete the ad by issuing the following command:
curl -X DELETE http://localhost:8080/ads/1
Incredible, huh? After scaffolding your application, you just had to create two things (the
Ad
entity and the AdRepository
interface), and you got a runnable RESTful API.Data Validation
After seeing Spring Data REST in action, you might be thinking: "Ok, this library does facilitate creating RESTful APIs, but I need to validate the data before persisting it to the database. Will it be easy to validate my data?" The answer to that is yes! By using the Bean Validation Java specification, validating your data is as easy as adding some annotations to the fields in your JPA entity.
“The Bean Validation specification allows you to validate data in your Spring Boot APIs very easily.”
Tweet This
If you want some proof, open the
Ad
class and update its code to look like this:// ./src/main/java/com/auth0/ads/Ad.java package com.auth0.ads; import lombok.EqualsAndHashCode; import lombok.Getter; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.math.BigDecimal; @Entity @Getter @EqualsAndHashCode public class Ad { @Id @GeneratedValue(strategy = GenerationType.AUTO) public Long id; @NotNull(message = "Who is the owner of this ad?") public String owner; @NotNull(message = "Please, inform a title for your ad.") @Size(min = 5, max = 140, message = "Titles must have between {min} and {max} characters.") public String title; @NotNull(message = "Please, inform a description for your ad.") @Size(min = 5, max = 500, message = "Titles must have between {min} and {max} characters.") public String description; @Min(message = "Price cannot be negative", value = 0) public BigDecimal price; protected Ad() {} public Ad(String owner, String title, String description, BigDecimal price) { this.owner = owner; this.title = title; this.description = description; this.price = price; } }
On the new version of this entity, you are importing and using three new annotations:
: This annotation allows you to make a field accept only data that has a minimum value. For example, on theMin
class, you are making sure you won't get negative (Ad
) values for the@Min(..., value = 0)
field.price
: This annotation allows you to mark fields not to acceptNotNull
values. In the case above, you are markingnull
,owner
, andtitle
as required fields.description
: This annotation allows you to make a field accept only values that have a length between a predefined range. For example, on the code above, you are making sure you won't persist ads with aSize
that contains less thantitle
characters or more than5
characters.140
After updating the
Ad
class, you can rerun your application (by issuing ./gradlew bootRun
or by pressing the start button on your IDE). When the app finishes booting, you can issue requests like the following to confirm that the restrictions are working:# won't work, too few characters on the title property curl -X POST -H "Content-Type:application/json" -d '{ "owner": "me", "title": "Abc", "description": "Pretty awesome but, unfortunately, I have to sell it.", "price": 500 }' http://localhost:8080/ads # won't work, negative price curl -X POST -H "Content-Type:application/json" -d '{ "owner": "me", "title": "My Car", "description": "Pretty awesome but, unfortunately, I have to sell it.", "price": -10 }' http://localhost:8080/ads # won't work, no description curl -X POST -H "Content-Type:application/json" -d '{ "owner": "me", "title": "My Car", "price": 500 }' http://localhost:8080/ads # will work, all properties contain valid values curl -X POST -H "Content-Type:application/json" -d '{ "owner": "me", "title": "My Car", "description": "Pretty awesome but, unfortunately, I have to sell it.", "price": 500 }' http://localhost:8080/ads
While issuing the invalid requests above (i.e., the first three), you will see that your app returns a response that looks like this:
{ "timestamp": "2019-02-19T18:35:52.094+0000", "status": 500, "error": "Internal Server Error", "message": "Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction", "path":"/ads" }
Although this response is sufficient for the calling client to understand that something went wrong, it doesn't help to identify what the problem was. To fix that, you can create a new class called
RestExceptionHandler
inside the com.auth0.ads
package and add the following code to it:// ./src/main/java/com/auth0/ads/RestExceptionHandler.java package com.auth0.ads; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.TransactionSystemException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.persistence.RollbackException; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.List; import java.util.stream.Collectors; @Order(Ordered.HIGHEST_PRECEDENCE) @ControllerAdvice public class RestExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(TransactionSystemException.class) protected ResponseEntity<List<String>> handleTransactionException(TransactionSystemException ex) throws Throwable { Throwable cause = ex.getCause(); if (!(cause instanceof RollbackException)) throw cause; if (!(cause.getCause() instanceof ConstraintViolationException)) throw cause.getCause(); ConstraintViolationException validationException = (ConstraintViolationException) cause.getCause(); List<String> messages = validationException.getConstraintViolations().stream() .map(ConstraintViolation::getMessage).collect(Collectors.toList()); return new ResponseEntity<>(messages, HttpStatus.BAD_REQUEST); } }
With this class, you are creating and registering a global exception handler (
ResponseEntityExceptionHandler
) for your application. Then, inside this handler, you are defining a method that will catch instances of TransactionSystemException
to unwrap the real problem (ex.getCause()
). If the unwrapped exception is not an instance of RollbackException
, then this method throws the underlying exception.If the unwrapped problem is an instance of
RollbackException
, then this method unwraps the cause one more time (cause.getCause()
). This time, the goal is to check if the second unwrap will result in an instance of ConstraintViolationException
. If this is not the case, the unwrapped result is thrown. If this is the case, this method generates a list of messages
to send back as a response.After creating this exception handler, if you restart your app and issue bogus requests, you will get some friendly messages. For example, if you issue the following request:
curl -X POST -H "Content-Type:application/json" -d '{ "owner": "me", "title": "My Car", "price": -10 }' http://localhost:8080/ads
You will get a list of messages saying that the "price cannot be negative" and to "please, inform a description for your ad." Not to hard too validate your data in this stack, huh?
Securing Spring Data REST APIs
Cool, with a minimum amount of effort you were able to create a backend application that exposes a bunch of RESTful APIs and, in a couple of minutes, you were able to extend it with data validation. However, what about security? There is no production-ready RESTful API without security, right?
Luckily, adding a security layer to your current stack is not hard either. To do this, you will add the Spring Security framework on top of your application and will use it with Auth0 to generate and validate access tokens.
For starters, you will need to open the
build.gradle
file of your project and update it as follows:// ... leave everything else untouched ... dependencies { // ... leave the other dependencies untouched ... implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.1.2.RELEASE' }
Here, you are making your project import two new dependencies. The first one is the Spring Boot extension for the Spring Security framework. The second one is a library that facilitates configuring OAuth (the authorization framework) which is used by Auth0.
After importing these new dependencies, you will have to:
- Add a couple of properties to the
file to configure these dependencies.application.properties
- Create a class to define how the endpoints will be accessible.
However, before doing so, you will need to configure an Auth0 API to represent you RESTful backend.
Note: If you don't have an Auth0 account yet, now is a good time to sign up for a free one.
After signing in (or signing up, if you didn't have an Auth0 account yet), head to the APIs section of your Auth0 Dashboard and click on the Create API button. When you click on this button, Auth0 will show you a dialog where it will ask you for three things:
- Name: A friendly name for your API. As this is just used inside the Auth0 Dashboard itself, don't worry much about this value (e.g., you can use something like "Spring Data REST Tutorial").
- Identifier: A logical identifier for the API you are creating. As Auth0 recommends using an URL-like value, you can add something like
here (although this looks like an URL, Auth0 will never call it).https://ads-api
- Signing Algorithm: Leave this set to
.RS256
After filling this form, click on the Create button. Then, back to your project, open the
application.properties
file and add the following to it:# ./src/main/resources/application.properties security.oauth2.resource.jwk.keySetUri=https://${YOUR-AUTH0-DOMAIN}/.well-known/jwks.json security.oauth2.resource.id=https://ads-api
You will have to replace the
${YOUR-AUTH0-DOMAIN}
placeholder with your Auth0 domain (e.g., blog-samples.auth0.com
), and that you will have to make sure the resource.id
property points to the identifier you used while registering your API.Note: Not sure what your Auth0 domain is? When you create a new account with Auth0, you are asked to pick a name for your Tenant. This name, appended with
, will be your Auth0 domain. For more information, please, check the Learn the Basic doc.auth0.com
With that in place, you will create a new class called
SecurityConfig
inside the com.auth0.ads
package and add the following code to it:// ./src/main/java/com/auth0/ads/SecurityConfig.java package com.auth0.ads; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; @Configuration @EnableResourceServer public class SecurityConfig extends ResourceServerConfigurerAdapter { @Value("${security.oauth2.resource.id}") private String resourceId; @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .mvcMatchers("/ads/**").authenticated(); } @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(resourceId); } }
The code for this class is quite simple. As you can see, you are using some Spring Boot annotations (like
@Configuration
and @Value
) to make Spring configure this class while bootstrapping the app. Also, you are defining two configure
methods to define how the Spring Security framework must behave. The first configure
method is telling the framework that all calls to /ads/**
must be authenticated. The second one is telling the ResourceServerSecurityConfigurer
to use, as the resourceId
, the value you added to the security.oauth2.resource.id
property.After creating your Auth0 API and making the changes described above, you can restart your application one more time and issue the following request:
curl http://localhost:8080/ads
This request (or any other aiming endpoints starting with
/ads
that do not carry an access token) will result in the following response:{ "error":"unauthorized", "error_description":"Full authentication is required to access this resource" }
To be able to issue valid (or authenticated) requests, you will need an access token. The process of getting an access token will depend on what type of client you are dealing with. This is out of scope here but, if you are dealing with a SPA application (like those created with React, Angular, and Vue.js), you can check this article to learn how to proceed. If you are dealing with some other type of client (e.g., regular web application or native application), check the Auth0's docs for more info.
Nevertheless, to see the whole thing in action, you can head back to your Auth0 Dashboard, open the API you just created, and move to the Test section. On this section, you will see a button called Copy Token that will provide you a temporary token that you can use to test your API.
So, click on this button and then use your client (or the terminal) to issue an HTTP request to your API with the test token:
# use the token copied to set the TOKEN variable TOKEN=eyJ...DRA # issue an authenticated HTTP request curl -H 'Authorization: Bearer '$TOKEN http://localhost:8080/ads
If everything works as expected, you will be able to consume your API endpoints again. Awesome, right?
“Developing production-ready RESTful APIs with Spring Data REST and Spring Boot is extremely easy!”
Tweet This
Conclusion
In this article, you learned about how easy it is to develop RESTful API with Spring Data REST and Spring Boot. More specifically, you started by using the Spring Initializr website to scaffold a new application. After that, you used Spring Data REST to expose API endpoints to manipulate a JPA entity. Then, in the end, you learned how to validate data and how to secure your API.
With that in place, you are ready to start developing production-ready, secure APIs with Spring Boot, Spring Data REST, and Auth0.
Was this fast (and fun) enough for you? Let us know in the comments section below.