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:

Java Program to Use Dynamic Programming to Solve Approximate String Matching
The Thread.join() Method in Java
Quick Guide to @RestClientTest in Spring Boot
Custom Cascading in Spring Data MongoDB
Java Program to Find the Longest Subsequence Common to All Sequences in a Set of Sequences
Java Program to Implement Sieve Of Sundaram
Prevent Brute Force Authentication Attempts with Spring Security
Converting Between a List and a Set in Java
Java Program to Search for an Element in a Binary Search Tree
Java Program to Perform Encoding of a Message Using Matrix Multiplication
Spring Data – CrudRepository save() Method
Java Program to Implement Bellman-Ford Algorithm
Debug a HttpURLConnection problem
Remove All Occurrences of a Specific Value from a List
Spring 5 Functional Bean Registration
Deploy a Spring Boot WAR into a Tomcat Server
The “final” Keyword in Java
A Guide to Apache Commons Collections CollectionUtils
Java Program to Optimize Wire Length in Electrical Circuit
Java Program to Check the Connectivity of Graph Using BFS
Java Program to Encode a Message Using Playfair Cipher
Spring MVC Async vs Spring WebFlux
Java Program to Implement LinkedBlockingDeque API
Spring Boot - Application Properties
Java Program to Find Hamiltonian Cycle in an UnWeighted Graph
Java Program to Implement Hash Trie
Generate Spring Boot REST Client with Swagger
A Guide to Java 9 Modularity
A Guide to LinkedHashMap in Java
Command-Line Arguments in Java
Spring Boot - CORS Support
Lập trình đa luồng với Callable và Future trong Java