The Registration API becomes RESTful

1. Overview

In the last few articles of the Registration series here on VietMX’s Blog, we built most of the functionality we needed in a MVC fashion.

We’re going to now transition some of these APIs to a more RESTful approach.

2. The Register Operation

Let’s start with the main Register operation:

@PostMapping("/user/registration")
public GenericResponse registerUserAccount(
      @Valid UserDto accountDto, HttpServletRequest request) {
    logger.debug("Registering user account with information: {}", accountDto);
    User registered = createUserAccount(accountDto);
    if (registered == null) {
        throw new UserAlreadyExistException();
    }
    String appUrl = "http://" + request.getServerName() + ":" + 
      request.getServerPort() + request.getContextPath();
   
    eventPublisher.publishEvent(
      new OnRegistrationCompleteEvent(registered, request.getLocale(), appUrl));

    return new GenericResponse("success");
}

So – how is this different form the original MVC-focused implementation?

Here goes:

  • the Request is now correctly mapped to a HTTP POST
  • we’re now returning a proper DTO and marshalling that directly into the body of the Response
  • we’re no longer dealing with error handling in the method at all

We’re also removing the old showRegistrationPage() – as that’s not needed to simply display the registration page.

3. The registration.html

With these changes, we now need to modify the registration.html to:

  • use Ajax to submit the registration form
  • receive the results of the operation as JSON

Here goes:

<html>
<head>
<title th:text="#{label.form.title}">form</title>
</head>
<body>
<form action="/" method="POST" enctype="utf8">
    <input  name="firstName" value="" />
    <span id="firstNameError" style="display:none"></span>
 
    <input  name="lastName" value="" />
    <span id="lastNameError" style="display:none"></span>
                     
    <input  name="email" value="" />           
    <span id="emailError" style="display:none"></span>
     
    <input name="password" value="" type="password" />
    <span id="passwordError" style="display:none"></span>
                 
    <input name="matchingPassword" value="" type="password" />
    <span id="globalError" style="display:none"></span>
 
    <a href="#" onclick="register()" th:text="#{label.form.submit}>submit</a>
</form>
             
 
<script src="jquery.min.js"></script>
<script type="text/javascript">
var serverContext = [[@{/}]];

function register(){
    $(".alert").html("").hide();
    var formData= $('form').serialize();
    $.post(serverContext + "/user/registration",formData ,function(data){
        if(data.message == "success"){
            window.location.href = serverContext +"/successRegister.html";
        }
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("MailError") > -1)
        {
            window.location.href = serverContext + "/emailError.html";
        }
        else if(data.responseJSON.error.indexOf("InternalError") > -1){
            window.location.href = serverContext + 
              "/login.html?message=" + data.responseJSON.message;
        }
        else if(data.responseJSON.error == "UserAlreadyExist"){
            $("#emailError").show().html(data.responseJSON.message);
        }
        else{
            var errors = $.parseJSON(data.responseJSON.message);
            $.each( errors, function( index,item ){
                $("#"+item.field+"Error").show().html(item.defaultMessage);
            });
            errors = $.parseJSON(data.responseJSON.error);
            $.each( errors, function( index,item ){
                $("#globalError").show().append(item.defaultMessage+"<br>");
            });
 }
}
</script>
</body>
</html>

4. Exception Handling

Along with the more RESTful API, the exception handling logic will of course become more mature as well.

We’re using the same @ControllerAdvice mechanism to cleanly deal with exceptions thrown by the application – and now we need to a new type of exception.

This is the BindException – which is thrown when the UserDto validated (if invalid). We’ll override the default ResponseEntityExceptionHandler method handleBindException() to add the errors in the response body:

@Override
protected ResponseEntity<Object> handleBindException
  (BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    logger.error("400 Status Code", ex);
    BindingResult result = ex.getBindingResult();
    GenericResponse bodyOfResponse = 
      new GenericResponse(result.getFieldErrors(), result.getGlobalErrors());
    
    return handleExceptionInternal(
      ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}

We will also need to handle our custom Exception UserAlreadyExistException – which is thrown when the user to register with an email that already exists:

@ExceptionHandler({ UserAlreadyExistException.class })
public ResponseEntity<Object> handleUserAlreadyExist(RuntimeException ex, WebRequest request) {
    logger.error("409 Status Code", ex);
    GenericResponse bodyOfResponse = new GenericResponse(
      messages.getMessage("message.regError", null, request.getLocale()), "UserAlreadyExist");
    
    return handleExceptionInternal(
      ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
}

5. The GenericResponse

We also need to improve the GenericResponse implementation to hold these validation errors:

public class GenericResponse {

    public GenericResponse(List<FieldError> fieldErrors, List<ObjectError> globalErrors) {
        super();
        ObjectMapper mapper = new ObjectMapper();
        try {
            this.message = mapper.writeValueAsString(fieldErrors);
            this.error = mapper.writeValueAsString(globalErrors);
        } catch (JsonProcessingException e) {
            this.message = "";
            this.error = "";
        }
    }
}

6. UI – Field and Global Errors

Finally, let’s see how to handle both field and global errors using jQuery:

var serverContext = [[@{/}]];

function register(){
    $(".alert").html("").hide();
    var formData= $('form').serialize();
    $.post(serverContext + "/user/registration",formData ,function(data){
        if(data.message == "success"){
            window.location.href = serverContext +"/successRegister.html";
        }
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("MailError") > -1)
        {
            window.location.href = serverContext + "/emailError.html";
        }
        else if(data.responseJSON.error.indexOf("InternalError") > -1){
            window.location.href = serverContext + 
              "/login.html?message=" + data.responseJSON.message;
        }
        else if(data.responseJSON.error == "UserAlreadyExist"){
            $("#emailError").show().html(data.responseJSON.message);
        }
        else{
            var errors = $.parseJSON(data.responseJSON.message);
            $.each( errors, function( index,item ){
                $("#"+item.field+"Error").show().html(item.defaultMessage);
            });
            errors = $.parseJSON(data.responseJSON.error);
            $.each( errors, function( index,item ){
                $("#globalError").show().append(item.defaultMessage+"<br>");
            });
 }
}

Note that:

  • If there are validation errors – then the message object contains the field errors and the error object contains global errors
  • We display each field error next to its field
  • We display all the global errors in one place at the end of the form

7. Conclusion

The focus of this quick article is to bring the API into a more RESTful direction and show a simple way of dealing with that API in the front end.

The jQuery front end itself is not the focus – just a basic potential client that can be implemented in any number of JS frameworks, while the API remains exactly the same.

The full implementation of this tutorial can be found in the github project – this is an Eclipse based project, so it should be easy to import and run as it is.

Related posts:

JPA/Hibernate Persistence Context
Java Program to Find Nearest Neighbor for Static Data Set
Spring Boot - Internationalization
Guide to java.util.concurrent.Future
The HttpMediaTypeNotAcceptableException in Spring MVC
Comparing getPath(), getAbsolutePath(), and getCanonicalPath() in Java
Java Program to Check whether Graph is a Bipartite using BFS
Spring Boot - Exception Handling
Convert String to Byte Array and Reverse in Java
Mockito and JUnit 5 – Using ExtendWith
The SpringJUnitConfig and SpringJUnitWebConfig Annotations in Spring 5
Java String to InputStream
Java Program for Douglas-Peucker Algorithm Implementation
Java Program to Implement Sieve Of Eratosthenes
Java Program to Check if a Directed Graph is a Tree or Not Using DFS
Jackson Ignore Properties on Marshalling
Spring Security Basic Authentication
Configuring a DataSource Programmatically in Spring Boot
Simple Single Sign-On with Spring Security OAuth2
Validate email address exists or not by Java Code
Java Program to Find ith Largest Number from a Given List Using Order-Statistic Algorithm
Java Program to Implement Tarjan Algorithm
Java Program to Implement Hopcroft Algorithm
Java Program to Use Boruvka’s Algorithm to Find the Minimum Spanning Tree
String Operations with Java Streams
Spring WebFlux Filters
Java Program to Find Basis and Dimension of a Matrix
Guava – Join and Split Collections
Guide to @ConfigurationProperties in Spring Boot
Java Program to Implement Stein GCD Algorithm
Java Program to Implement Sieve Of Sundaram
Java Program to Implement Traveling Salesman Problem using Nearest neighbour Algorithm