Janos Pasztor

Building an API-driven software: Setting up the backend

In our previous episode we have discussed a broad overview of an API-driven software how I do it. In this installment I would like to run you through setting up the backend.

Pre-requisites

If you are not well versed in Java or OOP, you may want to do some additional reading. More specifically, I would recommend the following posts on this blog:

These posts will help you understand OOP better, and that, in turn, will help you with Java. And don’t worry too much, I will explain each step in detail.

Grab the source code

If you want to grab the source code of this article head on over to GitHub.

Installing a development environment

For a development environment I strongly recommend IntelliJ IDEA. If you are unfamiliar with heavy IDEs and only used lightweight editors, trust me on this. IntelliJ helps you with autocompleting your code and letting you know if you mistype something.

Once you have installed IntelliJ, go to File → New → Project and start a new Maven project. Maven here is the dependency manager of choice for our project. Alternative package managers like Gradle also exist, but for now let’s stick with Maven.

In the next step you will be asked for the group ID and artifact ID for the project. In Java these are similar to package names in other languages. It is customary to use your domain name as a group ID, such as at.pasztor. The artifact ID would be the project name, for example in my case it would be backend. So full package “coordinates” with the version would look like this: at.pasztor:backend:1.0-SNAPSHOT

Once you go through the process you will be greeted with a project that contains a few files. Most importantly, you will see a file called pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>at.pasztor</groupId>
    <artifactId>backend</artifactId>
    <version>1.0-SNAPSHOT</version>


</project>

This is your project file that contains all your build configuration, such as dependencies, build plugins, etc. For now it’s pretty empty, but we will change that soon. For now, you may receive a popup that prompts you to Auto-Import changes to your pom.xml, which I would strongly recommend to enable:

Writing a test application

For now, let’s create a simple demo to check if everything is working. Let’s create a file called src/main/java/TestApplication.java by right clicking on the src/main/java folder and selecting New → Java Class.

The contents of the files should be the following:

public class TestApplication {
    public static void main(String[] argv) {
        System.out.println("Hello world!");
    }
}

Pretty simple, but let’s break it down nonetheless. This is a class in Java, just like the OOP articles I recommended above. This class contains a single method which is declared static. Static is a special case because they are functions that can be called without creating an instance of the class. In other languages you might implement this as a simple function, but in Java everything needs to be in classes.

Now, the public static void main(String[] argv) method in Java is even more special, as it is a starting point for your application. The argv parameter itself contains the command line parameters, if any.

IntelliJ is very helpful, by clicking the play button next to the method you can simply run the application:

This will pop up a little terminal window with the program running in it.

Setting up compiler basics

If the application compiles successfully we can now proceed to do some changes to pom.xml to make it behave the way we want it. First of all, we need to tell the Java compiler what language level we want it to use. In our case, we should at the very least set Java version 8:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>at.pasztor</groupId>
    <artifactId>backend</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>
</project>

Setting up Spring Boot

Now comes the moment where we actually start setting up our web application. First of all let’s pull in the Spring Boot web starter by adding the following section:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
    </dependencies>

This pulls in all the packages that are needed to create a web application with Spring Boot. In addition we will need to pull in the Spring Boot starter parent as a “parent” package so our package inherits the default settings that are required to compile Spring Boot applications properly.

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

Note: Packages in Maven can be hierarchical. The child packages inherit all the properties of parent packages. This is being used here so you don’t have to do a lot of configuration in Maven yourself.

As a final step to see it in action let’s delete the TestApplication and instead create a class called at.pasztor.backend.BlogApplication. This will create the appropriate folders for this package and place the BlogApplication class inside. (You can of course opt to use your own package name.)

Warning! Never place the application in the default namespace as that will cause all sorts of problems!

Note: The package name at.pasztor.backend corresponds to the group ID and artifact ID in pom.xml. This is purely for convenience, but there is no rule enforcing this.

The BlogApplication class should look as follows:

package at.pasztor.backend;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BlogApplication {
    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args);
    }
}

If you now start this application and go to http://localhost:8080 you will see a less than glorious error page, but nevertheless, this is already our backend application working.

Writing our first controller

Now comes the fun part: let’s actually do something useful. Let’s create a class named IndexController next to the BlogApplication with the following content:

package at.pasztor.backend;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @RequestMapping("/")
    public String index() {
        return "Hello world!";
    }
}

Now, in order to apply the changes we will need to restart the application. This can be done by hitting the restart button next to the little terminal window the application is running in:

Once the server is restarted you should see the results on the screen. Now, what exactly did we do?

The @Controller or @RestController annotations tell Spring Boot to consider this class as a recipient of requests from users. The @RequestMapping annotation on the other hand contains the actual routing information on requests that should be routed to the method.

JSON responses

So what if we want to send back a JSON response instead of a text document? Let’s create a test response object as follows:

package at.pasztor.backend;

public class TestResponse {
    public final String message;

    public TestResponse(String message) {
        this.message = message;
    }
}

This will be our message container. The constructor will accept our parameter and store it in the message variable. This variable is declared final for immutability. Our controller will be changed as follows:

package at.pasztor.backend;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    @RequestMapping(
            value = "/",
            produces = "application/json"
    )
    public TestResponse index() {
        return new TestResponse("Hello world!");
    }
}

And just like that you will receive a JSON response after reloading the application. Spring Boot uses a library called jackson-databind to automatically convert objects into JSON representations and back. It also provides helpful annotations such as @JsonIgnore to influence how the JSON is generated.

Consuming input parameters

An API isn’t the most useful without the user being able to send us some data back, so let’s take a look how to consume input parameters.

Path variables

Remember our example from the first episode? We said we wanted to have something like /books/1234. Here it may be useful to extract 1234 as a variable. In Spring Boot terms this is called path variable. Consider the following controller:

package at.pasztor.backend;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    @RequestMapping(
            value = "/books/{id}"
    )
    public String book(
            @PathVariable
            String id
    ) {
        return "This is book " + id;
    }
}

Here the @RequestMapping contains a variable in the form of {id}, and a parameter in the method contains the @PathVariable annotation. These two are matched up and the data type is converted automatically as needed.

Query parameters

The next option to consume input parameters are query strings. For example, you may want to provide a search option in the book listing like this: /books?search=Andersen

In this case our controller could look as follows:

package at.pasztor.backend;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    @RequestMapping(
            value = "/books"
    )
    public String search(
            @RequestParam
            String search
    ) {
        return "Searching for " + search;
    }
}

Now, this makes search a required parameter and if it is not passed, an error will be thrown. Alternatively you could also write the @RequestParam annotation like this: @RequestParam(required = false). This will make the parameter optional. Be careful though, if it is not passed, the variable will not be an empty string but a null and you may need to do a null-check. Therefore it is best to annotate optional parameters with @Nullable so that IntelliJ can warn you about missing null-checks:

package at.pasztor.backend;

import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    @RequestMapping(
            value = "/books"
    )
    public String search(
            @Nullable
            @RequestParam(required = false)
            String search
    ) {
        if (search == null) {
            return "Not searching for anything";
        } else {
            return "Searching for " + search;
        }
    }
}

Note on @Nullable: unfortunately there is no single standard on marking nullable variables in Java. There was a proposal called JSR-305, but it went nowhere and now each software vendor has their own @Nullable implementation. In our case we are using Spring Frameworks version, which IntelliJ can use to determine if a variable is nullable. For more information on nullability read my post called “A few words on null”.

Request body

The next option, for post requests for example, is to consume the request body with a data structure. So far we have only been using GET requests from HTTP, but now it is time to introduce others, like POST, which are used for, for example, form submissions.

In HTTP each request and response with a body should have a Content-Type associated with it. For classic web forms this content type is application/x-www-form-urlencoded and the request has the following format:

author=Hans+Christian+Andersen&title=The+Emperor's+New+Clothes

It is a bit hard to make out the parts of it, but basically it has the following format:

variable=[value, urlencoded]&nextvariable=[]&...

To consume these type or requests we can use the previously already introduced @RequestParam annotation:

package at.pasztor.backend;

import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {

    @RequestMapping(
            value = "/books",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
    )
    public String create(
            @RequestParam
            String title,
            @RequestParam
            String author
    ) {
        return "Created a book called " + title + " by " + author;
    }
}

However, if you now want to test this you will need something like the Advanced Rest Client as POST requests can’t be submitted without a form.

Alternatively, if you are well versed in the console, you can use Telnet:

$ telnet localhost 8080
POST /books HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Connection: close

author=Hans+Christian+Andersen&title=The+Emperor's+New+Clothes

Now, the situation becomes slightly more tricky when it comes to submitting JSON requests as is customary with APIs in general. In this case we have to create a request object:

package at.pasztor.backend;

public class CreateBookRequest {
    public final String title;
    public final String author;

    public CreateBookRequest(String title, String author) {
        this.title = title;
        this.author = author;
    }
}

Then the controller can look as follows:

package at.pasztor.backend;

import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.*;

@RestController
public class BookController {
    @RequestMapping(
            value = "/books",
            consumes = MediaType.APPLICATION_JSON_VALUE
    )
    public String create(
            @RequestBody
            CreateBookRequest request
    ) {
        return "Created a book called " + request.title + " by " + request.author;
    }
}

The @RequestBody annotation tells Spring Boot to decode the request body to an object, whereas the consumes parameter tells Spring Boot to route requests with the JSON type to this method.

Tip: you can have multiple methods with different consumes values to hande different data types. Spring Boot will route the requests accordingly. Similarly, the produces stanza can be used to indicate the produced response type and the client can decide using the Accept header which response type it wants.

Headers

Finally, let’s talk about consuming headers. These are very simple, the @RequestHeader annotation does the trick here.

Handling errors

If during your experiments you have tried to send a request that is incomplete, or you tried to access an URL that is not mapped, you may have received an error like this:

{
  "timestamp": "2019-10-13T18:21:01.690+0000",
  "status": 400,
  "error": "Bad Request",
  "message": "Required String parameter 'title' is not present",
  "path": "/books"
}

While this is useful, you may prefer to have your own error handling, or ideally convert exceptions into output directly. Generally there are two cases we want to handle:

  1. Our application threw an exception, which should be output to the user in a reasonable fashion.
  2. Spring Boot threw an exception, e.g. the above Bad Request exception.

For both cases we will need a bit of preparation work. First of all, let’s create a class called ApiException like this:

package at.pasztor.backend;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.http.HttpStatus;

public class ApiException extends Exception {
    //Don't output this to the JSON, this will be used only as a HTTP status.
    @JsonIgnore
    public final HttpStatus status;
    public final ErrorCode error;
    public final String errorMessage;

    public ApiException(HttpStatus status, ErrorCode error, String errorMessage) {
        this.status = status;
        this.error = error;
        this.errorMessage = errorMessage;
    }

    public enum ErrorCode {
        TEST,
        NOT_FOUND
    }
}

This will allow us to throw exceptions that are then properly converted to JSON in a manner that we control, with the error field containing the error code and the errorMessage field containing the actual error message. Having errors like this is extremely useful to anyone consuming our API, including our own frontend. The enum will list all possible values for the error code, which makes it less of a guesswork to use the API.

Now we need to build an error handler. First of all, let’s handle exceptions from our own application:

package at.pasztor.backend;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Controller
@ControllerAdvice
public class ErrorController {
    @ExceptionHandler(value = { ApiException.class })
    public ResponseEntity<ApiException> onException(ApiException apiException) {
        return new ResponseEntity<>(
            apiException,
            apiException.status
        );
    }
}

The @ControllerAdvice annotation will make this class a handler for all exceptions, while the @ExceptionHandler will declare the method that handles exceptions of the type ApiException. Other exceptions will be handled by the built-in exception handler.

The ResponseEntity we return will invoke Spring Boots automatic object conversion. In other words, any ApiException will be run through Jackson to be converted to JSON.

Now, let’s create a test controller to throw an exception:

package at.pasztor.backend;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ExceptionTestController {
    @RequestMapping("/exception")
    public void test() throws ApiException {
        throw new ApiException(
                HttpStatus.INTERNAL_SERVER_ERROR,
                ApiException.ErrorCode.TEST,
                "This is a test."
        );
    }
}

If you call the URL http://localhost:8080/exception you will, quite surprisingly, see the following:

{
cause: null,
stackTrace: [
  //...
],
error: "TEST",
errorMessage: "This is a test.",
message: null,
localizedMessage: null,
suppressed: [ ]
}

These extra fields are in the response because they are properties of exceptions in Java. These are not useful to us, and indeed the stack trace could leak information about our application to the user. To hide the extra parameters we can change the ApiException class to have Jackson ignore those parameters:

package at.pasztor.backend;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.http.HttpStatus;

@JsonIgnoreProperties(value = {
        "detailMessage",
        "cause",
        "stackTrace",
        "suppressedExceptions",
        "backtrace",
        "message",
        "localizedMessage",
        "suppressed"
})
public class ApiException extends Exception {
    @JsonIgnore
    public final HttpStatus status;
    public final ErrorCode error;
    public final String errorMessage;

    public ApiException(HttpStatus status, ErrorCode error, String errorMessage) {
        this.status = status;
        this.error = error;
        this.errorMessage = errorMessage;
    }

    public enum ErrorCode {
        TEST,
        NOT_FOUND
    }
}

The @JsonIgnoreProperties annotation will cause Jackson to ignore the specified fields and let us produce a clean output.

Now that we have our exception handler in place, we can change the ErrorController to include handling of Spring Boot internal errors, such as when a route was not found:

package at.pasztor.backend;


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

@Controller
@ControllerAdvice
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping(
            value = "/error",
            produces = "application/json"
    )
    public ResponseEntity<Void> errorJson(HttpServletRequest request) throws ApiException {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

        if (status != null) {
            int statusCode = Integer.parseInt(status.toString());
            switch (statusCode) {
                case 404:
                    throw new ApiException(
                            HttpStatus.NOT_FOUND,
                            ApiException.ErrorCode.NOT_FOUND,
                            "The requested API endpoint was not found."
                    );
            }
            return new ResponseEntity<>(HttpStatus.valueOf(statusCode));
        }
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(value = { ApiException.class })
    public ResponseEntity<ApiException> onException(ApiException apiException) {
        return new ResponseEntity<>(
            apiException,
            apiException.status
        );
    }
}

As you can see in the code, we specify that /error is the alias for the error page, and below that we specify a regular @RequestMapping for said URL. In this variant we are producing a JSON output, but as before, you could create a second method for producing HTML output.

In the error page method we try to find out what the cause of the error is. If it is a 404 (not found) we simply throw an ApiException to that effect, which will be caught and processed by our exception handler.

Next up

In the next installment of this series we will take a look at connecting a database. We will start writing a blog application, store and retrieve blog posts.

Janos Pasztor

I'm a DevOps engineer with a strong background in both backend development and operations, with a history of hosting and delivering content.

I run an active DevOps and development community on Discord, come in and say hi!

Join the community

Discord

Subscribe

Facebook Facebook Twitter Twitter GitHub GitHub
YouTube YouTube RSS Atom Feed
Do you want more? Click the buttons below!