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 Check if a Given Binary Tree is an AVL Tree or Not
Check If a String Is Numeric in Java
Java Program to implement Array Deque
Java NIO2 Path API
Java Program to Perform Cryptography Using Transposition Technique
Retrieve User Information in Spring Security
Guide to BufferedReader
Predicate trong Java 8
Java Program to Find Number of Spanning Trees in a Complete Bipartite Graph
Java Program to Generate Random Numbers Using Probability Distribution Function
Configure a RestTemplate with RestTemplateBuilder
Java Program to Find a Good Feedback Edge Set in a Graph
Iterable to Stream in Java
Java Program to Implement Levenshtein Distance Computing Algorithm
Sử dụng Fork/Join Framework với ForkJoinPool trong Java
ThreadPoolTaskExecutor corePoolSize vs. maxPoolSize
Spring REST API + OAuth2 + Angular (using the Spring Security OAuth legacy stack)
Java Web Services – Jersey JAX-RS – REST và sử dụng REST API testing tools với Postman
Hướng dẫn Java Design Pattern – MVC
Java Program to Implement Double Order Traversal of a Binary Tree
Hướng dẫn Java Design Pattern – DAO
Java 8 and Infinite Streams
Java Program to Implement Doubly Linked List
Guide to the Fork/Join Framework in Java
Java Program to Implement Interpolation Search Algorithm
Getting Started with Forms in Spring MVC
Java Program to Find Inverse of a Matrix
JPA/Hibernate Persistence Context
Loại bỏ các phần tử trùng trong một ArrayList như thế nào trong Java 8?
Java Program to Compute the Area of a Triangle Using Determinants
Java Program to Perform Complex Number Multiplication
Spring Boot - Creating Docker Image