DEV Community

Cover image for A better way to create backends - REST API in java
Gabriele Boccarusso
Gabriele Boccarusso

Posted on • Originally published at boccarusso.com

A better way to create backends - REST API in java

APIs are a hot topic in the development world and for a good reason: They provide easy access to internal software operations and abstract many things away.

API stands for Application Programming Interface and their ease to use can already be understood by the name itself. They are just interfaces to abstract operations to avoid writing them all over again by just furnishing the data needed for the task.
APIs are used everywhere and the first example can be seen in every programming language with their basic functions. When you use Java and type

System.out.println("Hello World!")

or in python

print("Hello World!")

you are actually using the internal API of the language. By passing an argument like "Hello World" an internal function of the language is connecting it with the terminal and running the internal operations needed to communicate successfully with the terminal. Every built-in function can be considered an API, but the ones we're going to talk, albeit similar in principle, differ in the form and other characteristics.

Principles

REST is an API architecture. The acronym stands for REpresentational State Transfer and follows five principles that define its features:

  1. Uniformity: every request for the same data look the same and belongs to a single resources identifier
  2. Statelessness: every request has to contain all the necessary information and can't contain any server side session data
  3. Cacheability: resources should be cacheable on the server side as much as possible to improve client performance and server scalability
  4. Layerness: the client and the server are not directly connected but there may be layers in the communication
  5. Independency: client and server are indipendent between each other. The only resource known to the client is the resource identifier.

Communication

REST APIs communicate only using HTTP requests to perform database functions and queries along other operations. The response can be served in any format. The most commons are:

  • JSON
  • XML
  • CSV
  • Plain text

Anatomy

Every REST API call has an URI (Uniform Resource identifier) and a server performs the resource identification by using a resource Locator, an URL. All of this happens through the HTTP protocol. The 4 most common methods are GET, to get a resource, POST, to create it, PUT to modify it and DELETE, to delete it.
An HTTP call has also headers: metadata exchanged with the server to indicate the method, the status, the response and sending format, cookies parameters to recognize a user quickier and many more

Authentication

A RESTful service authenticates requests by veryfing the identity of the sender. There are four ways to authenticate:

Basic HTTP Authentication

The client sends user and password to the server encoded in base64.

Bearer HTTP Authentication

A bearer token is an encrypted string generated by the server for a login request for then the client sending the token in the request header to access resources. This authentication should be used only with HTTPS. Sensitive data would otherwise be really easy to be intercepted and used by third parties.

API keys

An API key is an identifier assigned to a single user just one time and that needs to be present in every API call. They are prone to networking theft although their main scope is to identify who is making the request to limit or control their usage of the server services.

OAuth

The server asks first for password and username for then asking an additional token sent through email or sms (most common ways) to verify the sender identity. It is also called two factor authentication

REST response

A REST response is made of a status line which contains the result:

  • 1XX means that the request is in process.
  • 2XX means success.
  • 3XX means redirection. Another service has to fulfill the request.
  • 4XX is a client error and the request is either incomplete or in the wrong syntax.
  • 5XX means a server error and may happen even if the request is valid.
Along the status line there is also the body of the request. It contains the resource representation based on the format contained in the header (JSON, XML, plain text). Lastly, there is the header which contains metadata about the content type, the encoding, the date, hour and many more

Spring boot and the MVC framework

As there are many ways to reach the same ends there are many frameworks and languages to develop an API. One of the best out there is java with spring boot due the ease of use and great flexibility it gives along the great resource management of the language itself.

To initialize a spring boot API project we need three dependencies:

  • Spring Web so that the server can listen and respond to requests
  • Spring Data JPA to interact with the database
  • H2 Database to have an actually testing database
H2 is a great tool for testing and using a database from the ground up. Spring Boot JPA abstracts many things away so that it does not matter what database is actually in use the code will remain the same. Changing from H2 to databases like MySQL and PostGreSQL involves only the configuration of attacching the database to the API.
Using maven all of this dependencies will be added in the pom.xml file as XML code like this:
</dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

while using gradle they'll be added in the gradle.build file like this:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Notice that in any case the h2 database is set on runtime so that it can perform only while the application is running and lose every data once the application stops. H2 can be used as an actual database and not just for testing but is not very performant. Adding the Java DataBase Connection dependency to connect another database will not change any output at all.

The MVC architecture with REST APIs

The Model View Controller is a basic architecture which divides an application into three main components:

  • The Controller which gets the inputs, sanitize them if needed, and matches them to the related model.
  • The Model which gets the data and shapes them into the result.
  • The View which defines the final data format and returns it.
Although modern applications don't follow this model exactly it still defines a key concept: divide how to get the inputs from how to elaborate them.
With an API the code will be divided into:
  • The controller which receives the HTTP requests
  • The Repository which defines the operations that can be done on the database
  • The entity which defines the structure of a SQL table
In a more complicated API an additional layer can be introduced to sanitize or elaborate further the received inputs. In small applications this may happens directly in the controller although this way can quickly get out of hand if the application will ever grow too much.

Supposing that your Spring Boot application is named "playground", you can organize the structure of the files this way:

  • playground
  • playground.controller
  • playground.repository
  • playground.entity
playground is the package that contains PlaygroundApplication.java, the code that will start and run the entire project, and is made automatically by Spring Boot.
The other ones are going to be created by you. They all have playground in front so that can be recognized by Spring Boot as parts of the overall application.

Entities

An entity, as already stated, is just java definition of a SQL table. If you want to create a table in the database that contains data about people with three columns, id, firstName and lastName, then all you need is to create the class Person.java in the playground.entity package with the following code:

package playground.entity;

import java.util.UUID;

public class Person {
    private String id;
    private String firstName;
    private String lastName;
 
 // define the various constructors
 public Person() {
  this.setId(UUID.randomUUID().toString());
 }
 
 public Person(String id) {
  this.setId(id);
 }
 
 public Person(String id, String firstName) {
  this(id);
  this.setFirstName(firstName);
 }
 
 public Person(String id, String firstName, String lastName) {
  this (id, firstName);
  this.setLastName(lastName);
 } 
 // end of constructors

 // also define the setters and the getters
 public String getId() {
  return id;
 }

 public void setId(String id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
}

By itself this is just a class. To connect it any possible database we need to use the Javax Persistence API in Spring Boot 2.X.X or Jakarta Persistence API in Spring Boot 3.0.0 so that it can be recognized as an entity through the following annotations:

package playground.entity;

import java.util.UUID;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

// annotate the following class has an entity (a.k.a. table)
@Entity
public class Person {
 // define this variable has an id
 @Id
 private String id;
 // set properties of the columns
 @Column(name = "first_name", length = 128)
 private String firstName;
 @Column(name = "last_name", length = 128)
 private String lastName;
 
...

Now that the entity is set just remain the operations that can be done with it.

Repositories

By going into the package playground.repository you can create PersonRepository.java and use many function straight out of the box:

package playground.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import playground.entity.Person;

// you create an interface and extends it with JpaRepositorys.
// the first object to be passed is the entity and the second is
// the type of the entity id.
public interface PersonRepository extends JpaRepository<Person, String> {
}

This few lines of codes are all that is needed to perform many SQL-like operations with the entity.

Controllers

The entire part dedicate to the database and how to interact with it is done. What's left is only the core part: how to actually make an API.
By going into playground.controller create the class PersonController with the following code:

package playground.controller;

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

import playground.repository.PersonRepository;

@RestController
@RequestMapping("person")
public class PersonController {
 private final PersonRepository personRepository;
 
 public PersonController(PersonRepository personRepository) {
  this.personRepository = personRepository;
 }
}

By default Spring Boot is uses the port 8080. By mapping our entiner class on person makes so that every mapping inside the controller needs to be preceded by it while initializing the relative repository in the constructor makes available all of the methods automatically created.
Going on http:localhost:8080/people you will receive an error screen. This because the class is still empty. Usually the result for the default view is all of the entries. Easily done with the now instantiated repo:

 @GetMapping
 Iterable allPeople() {
  return this.personRepository.findAll();
 }

By going on http://localhost:8080/person there should be no error now (if you haven't forgotten to import GetMapping and Person of course) but the screen appears completely void. This because there is nothing in the database. You can add some people by adding a bit of code in the constructor:

public PersonController(PersonRepository personRepository) {
  this.personRepository = personRepository;
  
  this.personRepository.saveAll(List.of(
     new Person("1", "John", "Doe"),
     new Person("2", "Mark", "Smith"),
     new Person("3", "Arnold", "Goodman")
    ));
}

Now that the database is a bit populated we also need a way to find a person with a certain id:

@GetMapping("/{id}")
 Optional<Person> getPersonById(@PathVariable String id) {
  return this.personRepository.findById(id);
 }

Using the repository interface the most important methods are already outlined and only a single line of code is needed to achieve what would have otherwise needed many lines of code and a lot of work.
Adding a person to the database is easy enough. We just put in the body a JSON representation of a Person while requesting the body in the POST mapping:

/*
  * possible JSON in the body:
  * {
  *  "id": "5",
  *  "firstName": "Lucy",
  *  "lastName": "Block"
  * }
  * */
 @PostMapping
 Person postPerson(@RequestBody Person person) {
  return this.personRepository.save(person);
}

For the PUT method we have to do something a bit more articulate: check if an entry with the same id exists. If not create a new one and otherwise change the alreay existing one.

 @PutMapping("/{id}")
 ResponseEntity<Person>putPerson(@PathVariable String id, @RequestBody Person person) {
  HttpStatus status = this.personRepository.existsById(id)
    ? HttpStatus.OK
    : HttpStatus.CREATED;
  
  return new ResponseEntity(this.personRepository.save(person), status);
}

The save() function does it automatically. All that there is need to do is to check if another entry with the same id exists and change the status accordingly. The difference between ExistsById and FindById is that the former returns a boolean while the latter an Optional.
HttpStatus is self explanatory. OK is the classic 200 while CREATED is 201.

Delete is, like everything else, pretty easy.

 @DeleteMapping("/{id}")
  void deletePerson(@PathVariable String id) {
    this.personRepository.deleteById(id);
}

Now that every piece is done let's take a look to the controller in its whole:

package playground.controller;

import java.util.List;
import java.util.Optional;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import playground.entity.Person;
import playground.repository.PersonRepository;

@RestController
@RequestMapping("person")
public class PersonController {
 private final PersonRepository personRepository;
 
 public PersonController(PersonRepository personRepository) {
  this.personRepository = personRepository;
  
  this.personRepository.saveAll(List.of(
     new Person("1", "John", "Doe"),
     new Person("2", "Mark", "Smith"),
     new Person("3", "Arnold", "Goodman")
    ));
 }
 
 @GetMapping
 Iterable<Person> allPeople() {
  return this.personRepository.findAll();
 }
 
 @GetMapping("/{id}")
 Optional<Person> getPersonById(@PathVariable String id) {
  return this.personRepository.findById(id);
 }
 
 /*
  * possible JSON in the body:
  * {
  *  "id": "5",
  *  "firstName": "Lucy",
  *  "lastName": "Block"
  * }
  * */
 @PostMapping
 Person postPerson(@RequestBody Person person) {
  return this.personRepository.save(person);
 }
 
 @PutMapping("/{id}")
 ResponseEntity<Person> putPerson(@PathVariable String id, @RequestBody Person person) {
  HttpStatus status = this.personRepository.existsById(id)
    ? HttpStatus.OK
    : HttpStatus.CREATED;
  
  return new ResponseEntity<Person>(this.personRepository.save(person), status);
 }
 
 @DeleteMapping("/{id}")
 void deletePerson(@PathVariable String id) {
  this.personRepository.deleteById(id);
 }
}

Testing the API

Now that everything is done we still need a way to actually see if the code has any errors and works the way it's intended.
To test an API there are many ways, from sending CURL requests via the command line to using things like HTTPie still through the command line. A better and more intuitive way is through Postman, an application which provide an UI to send API requests. It can be used both on the web and on the desktop. For now is better to download the client so to avoid some security issues. Once it is installed, running the API is very easy. In the root folder of the Spring Boot project there should be a program called mvnw. This is the maven wrapper so that it can be used without any installation. By using in the command line the command ./mvnw spring-boot:run maven will perform all the needed operations to start the spring boot application.
Once everything is done the API will be available by default on the port 8080. By going in Postman we can view all the entries with a simple GET request at http://localhost:8080/person


Getting all the entries from a spring boot API

Following the controller mapping, this becomes the default view. If we add an id to the URL we can get the related person. The result of http://localhost:8080/person/1 will get the person with an id of 1. An error 404 if no entry with such id is in the database.


Getting an etry with a specific id in a spring boot API

To make a POST request only the body is needed. Using the JSON format it'd be like this:


POSTing a resource in a spring boot API

The response 200 means that the entry has been added successfully and if we look for the newly added id we can see the proof:


Checking if a resource has been added in a spring boot API

Testing the PUT method is similar to POST with the exception that we put other data in the body:


PUTting a resource in a spring boot API

And if you check the resource id you'll see proof of the changing:


Checking if a resource has been PUTted successfully in a spring boot API

Deleting a resouces comes to the same way:


DELETEing a resource in a spring boot API

And by getting again all of the entries we'll see that the person with the id of 1 doesn't exists anymore:


Checking if a resource has been DELETEd successfully in a spring boot API

Wrapping it all up

Spring Boot has many functionalities and this was just the tip of the iceberg. Diving a bit deeper into JPA it's possible to create custom queries and joining tables between them. With other spring dependencies you can call another API to further divide the overall application into microservices, use reactive programming and dive deeper into the security of the application.
All of the code can be found here. More information can be found on the internet and on the official spring boot website

Top comments (0)