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 Give an Implementation of the Traditional Chinese Postman Problem
Send email with JavaMail
Guide to the Java Clock Class
A Guide to WatchService in Java NIO2
Apache Camel with Spring Boot
Using the Map.Entry Java Class
Guide to System.gc()
Java Program to Find the Shortest Path from Source Vertex to All Other Vertices in Linear Time
Java Program to Implement Bubble Sort
Java Program to Construct K-D Tree for 2 Dimensional Data
The DAO with JPA and Spring
Build a REST API with Spring and Java Config
Java Program to Check Whether a Given Point is in a Given Polygon
Java Program to Find MST (Minimum Spanning Tree) using Prim’s Algorithm
Java Program to Implement Levenshtein Distance Computing Algorithm
The StackOverflowError in Java
Setting Up Swagger 2 with a Spring REST API
Send email with SMTPS (eg. Google GMail)
Mockito and JUnit 5 – Using ExtendWith
Java Program to Perform Left Rotation on a Binary Search Tree
Quick Intro to Spring Cloud Configuration
Sử dụng Fork/Join Framework với ForkJoinPool trong Java
Java Program to Find MST (Minimum Spanning Tree) using Kruskal’s Algorithm
Java Program to Find the Shortest Path Between Two Vertices Using Dijkstra’s Algorithm
Java 8 Stream findFirst() vs. findAny()
Java Program to Implement Weight Balanced Tree
Java Program to Use Boruvka’s Algorithm to Find the Minimum Spanning Tree
Add Multiple Items to an Java ArrayList
Java Program to Describe the Representation of Graph using Incidence Matrix
Java Streams vs Vavr Streams
Java Program to Compute the Volume of a Tetrahedron Using Determinants
Hướng dẫn Java Design Pattern – Intercepting Filter