In the previous post, we exposed a repository as a RESTful interface using Spring Data REST. However, that’s not the conventional way of creating REST API for Spring Boot Microservices. That particular approach works best if your data model is exactly similar to your domain model. Usually, that’s not the case.

In this post, we will look at the right way of creating REST API using Spring Boot.

Data Transfer Objects for Quering Data

In a real production environment, it is often the case that our API model is drastically different to the actual database entity model.

To handle this situation, we create Data Transfer Objects. They are also commonly referred as DTOs.

First, we create a DTO for handling query APIs. Below is the VehicleQueryDTO.

public class VehicleQueryDTO {

    private UUID id;

    private String vehicleIdentityNumber;

    private String make;

    private String model;

    public VehicleQueryDTO(UUID id, String vehicleIdentityNumber, String make, String model) {
        this.id = id;
        this.vehicleIdentityNumber = vehicleIdentityNumber;
        this.make = make;
        this.model = model;
    }

    public UUID getId() {
        return id;
    }

    public String getVehicleIdentityNumber() {
        return vehicleIdentityNumber;
    }

    public String getMake() {
        return make;
    }

    public String getModel() {
        return model;
    }

    @Override
    public String toString() {
        return "VehicleQueryDTO{" +
                "id=" + id +
                ", vehicleIdentityNumber='" + vehicleIdentityNumber + '\'' +
                ", make='" + make + '\'' +
                ", model='" + model + '\'' +
                '}';
    }
}

Just for comparison sake, below is the actual Vehicle entity.

@Entity
@Table(name = "vehicle")
public class Vehicle {

    @Id
    private UUID id;

    private String vehicleIdentityNumber;

    private String make;

    private String model;

    private String status;

    public Vehicle() {
    }

    public UUID getId() {
        return id;
    }

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

    public String getVehicleIdentityNumber() {
        return vehicleIdentityNumber;
    }

    public void setVehicleIdentityNumber(String vehicleIdentityNumber) {
        this.vehicleIdentityNumber = vehicleIdentityNumber;
    }

    public String getMake() {
        return make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Vehicle{" +
                "id=" + id +
                ", vehicleIdentityNumber='" + vehicleIdentityNumber + '\'' +
                ", make='" + make + '\'' +
                ", model='" + model + '\'' +
                ", status='" + status + '\'' +
                '}';
    }
}

Notice that the Vehicle class has an extra field called Status. However, we don’t want to expose it. Therefore, we didn’t include it in the VehicleQueryDTO.

Services for Quering Data

Now that we have created a DTO for queries, we need to create a service. This service is required to map the actual entity model to the query API model.

First, we create an interface for the query service as below:

public interface VehicleQueryService {

    public VehicleQueryDTO getVehicle(UUID id);
    public List<VehicleQueryDTO> listAllVehicles();
}

Then, we implement the service as below:

@Service
public class VehicleQueryServiceImpl implements VehicleQueryService {

    @Autowired
    private VehicleRepository vehicleRepository;

    @Override
    public VehicleQueryDTO getVehicle(UUID id) {
        if (vehicleRepository.findById(id).isPresent()){
            Vehicle fetchedVehicle = vehicleRepository.findById(id).get();
            return new VehicleQueryDTO(fetchedVehicle.getId(), fetchedVehicle.getVehicleIdentityNumber(), fetchedVehicle.getMake(), fetchedVehicle.getModel());
        }else{
            return null;
        }
    }

    @Override
    public List<VehicleQueryDTO> listAllVehicles() {
        List<VehicleQueryDTO> vehicleList = new ArrayList<>();
        
        vehicleRepository.findAll().forEach(vehicle -> {
            vehicleList.add(new VehicleQueryDTO(vehicle.getId(), vehicle.getVehicleIdentityNumber(), vehicle.getMake(), vehicle.getModel()));
        });
        
        return vehicleList;
    }
}

Basically, we have implemented the two methods we defined in the service interface. One is for fetching an individual record from the database. The other is for fetching all records.

Notice that we have annotated this class with @Service. This allows Spring to discover the class for dependency injection when the Spring context is created during application start-up.

To interact with the actual table, we use Spring Data JPA’s repository. For the Vehicle entity, this is the VehicleRepository we defined earlier.

public interface VehicleRepository extends CrudRepository<Vehicle, UUID> {

    List<Vehicle> findByMake(@Param("make") String make);
}

We are using the default methods findById() and findAll(). These methods are part of the CrudRepository interface that is extended by our VehicleRepository.

In the latest Spring Data JPA spec, the findById() method returns an Optional. Basically, Optional wraps the actual object. In order to fetch the actual object, we can use the get() method.

REST Controllers for Querying Data

The last important part to create REST API is the Controller. It is simply a class annotated with @RestController annotation and appropriate methods to handle the HTTP calls.

Below is how it looks.

@RestController
@RequestMapping(value = "/api/vehicles")
public class VehicleQueryController {

    @Autowired
    private VehicleQueryService vehicleQueryService;

    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public ResponseEntity<List<VehicleQueryDTO>> listAllVehicles(){
        return new ResponseEntity<>(vehicleQueryService.listAllVehicles(), HttpStatus.OK);
    }

    @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.OK)
    public ResponseEntity<VehicleQueryDTO> getVehicle(@PathVariable(value = "id") UUID id){
        return new ResponseEntity<>(vehicleQueryService.getVehicle(id), HttpStatus.OK);
    }
}

Here we have wired the VehicleQueryService using @Autowired annotation. When the application starts up, Spring will discover the VehicleQueryServiceImpl and inject an instance of the class into the Controller class.

Let’s check out some of the important things in this class:

  • Annotation @RequestMapping is used to define the high-level path for our APIs. We declare it as /api/vehicles.
  • There are two methods. Both are annotated with @GetMapping. This means that these methods will be called at the time of HTTP GET request.
  • Annotation @ResponseStatus defines the response status code. In this case, we have specified it as HttpStatus.OK or 200.
  • Both the methods use the VehicleQueryService to fetch the result. Then, we wrap the result in a ResponseEntity and return it as output.
  • The method getVehicle() accepts an id property. We supply it as a path variable and access it using @PathVariable annotation. Then, we pass it to the getVehicle() method of the service.

Testing the REST API for Querying Data

While there are many sophisticated tools out there to test REST APIs, we can test simple GET methods directly from our browser.

However, before we test, we start the application using the below command clean package spring-boot:run.

Once the application has started up successfully, we can visit the browser and fire a request to the URL http://localhost:8080/api/vehicles

Since we are already inserting couple of records at startup time, you should see the below output:

If we want to access a particular vehicle, we can fire a request to the URL http://localhost:8080/api/vehicles/{UUID}. Then, we should see something like below:

Insert and Update Data – Upsert Data

We created a REST API using Spring Boot to query data stored in our database. Now, we will take the next step. In order to improve our sample application, we will create REST API using Spring Boot to Insert and Update Data in our database.

As per HTTP standards, Insert and Update correspond to the HTTP POST and HTTP PUT verbs. In typical RESTful standards, we treat entities as resources. POST method basically deals with creating a resource on the server. On the other hand, PUT deals with updating an existing resource on the server.

We will implement both methods in this post. So let’s start.

Data Transfer Objects for Upserting Data

In our sample app, Vehicles is the resource. The resource will have a business representation that might also be different from the actual persistence level representation. Also, this representation might vary for queries and commands.

To handle such a situation, we use Data Transfer Objects or DTOs. Basically, these DTO objects act as bridge between the persistence layer and the interface layer.

The first one would be the VehicleCreateDTO. This DTO has the bare minimum fields required to create a vehicle in our application.

public class VehicleCreateDTO {

    private String vehicleIdentityNumber;

    private String make;

    private String model;

    public String getVehicleIdentityNumber() {
        return vehicleIdentityNumber;
    }

    public void setVehicleIdentityNumber(String vehicleIdentityNumber) {
        this.vehicleIdentityNumber = vehicleIdentityNumber;
    }

    public String getMake() {
        return make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    @Override
    public String toString() {
        return "VehicleCreateDTO{" +
                "vehicleIdentityNumber='" + vehicleIdentityNumber + '\'' +
                ", make='" + make + '\'' +
                ", model='" + model + '\'' +
                '}';
    }
}

Then, we will create DTO for Update. We call it VehicleUpdateDTO. Important thing to note here is that we have only two fields. Basically, we are implying here that a consumer can only update the make and model of the vehicle.

public class VehicleUpdateDTO {

    private String make;

    private String model;

    public String getMake() {
        return make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    @Override
    public String toString() {
        return "VehicleUpdateDTO{" +
                "make='" + make + '\'' +
                ", model='" + model + '\'' +
                '}';
    }
}

Creating the Service Layer

Like last time, we will again create a service layer to handle the logic of inserting and updating. This time we will create a command service.

Let’s first declare the service interface.

public interface VehicleCommandService {

    public UUID createVehicle(VehicleCreateDTO vehicleCreateDTO);
    public VehicleQueryDTO updateVehicle(UUID id, VehicleUpdateDTO vehicleUpdateDTO);

}

Then, we create an implementation for this interface. In the implementation, we will actually implement the two methods declared above.

@Service
public class VehicleCommandServiceImpl implements VehicleCommandService {

    @Autowired
    private VehicleRepository vehicleRepository;

    @Override
    public UUID createVehicle(VehicleCreateDTO vehicleCreateDTO) {
        Vehicle newVehicle = new Vehicle();

        newVehicle.setId(UUID.randomUUID());
        newVehicle.setVehicleIdentityNumber(vehicleCreateDTO.getVehicleIdentityNumber());
        newVehicle.setMake(vehicleCreateDTO.getMake());
        newVehicle.setModel(vehicleCreateDTO.getModel());
        newVehicle.setStatus(String.valueOf(Status.FOR_SALE));

        return vehicleRepository.save(newVehicle).getId();
    }

    @Override
    public VehicleQueryDTO updateVehicle(UUID id, VehicleUpdateDTO vehicleUpdateDTO) {

        if (vehicleRepository.findById(id).isPresent()){
            Vehicle existingVehicle = vehicleRepository.findById(id).get();

            existingVehicle.setMake(vehicleUpdateDTO.getMake());
            existingVehicle.setModel(vehicleUpdateDTO.getModel());

            Vehicle updatedVehicle = vehicleRepository.save(existingVehicle);

            return new VehicleQueryDTO(updatedVehicle.getId(), updatedVehicle.getVehicleIdentityNumber(),
                    updatedVehicle.getMake(), updatedVehicle.getModel());
        }else{
            return null;
        }
    }

}

Let’s understand what we have done here:

  • We first wire the VehicleRepository interface. The Vehicle Repository extends the Spring Data JPA’s CrudRepository interface. The CrudRepository interface has the save() method.
  • Then, we implement the methods declared in the interface. First is the createVehicle() method. In this method, we retrieve the values from the VehicleCreateDTO object. Using those values, we build the Vehicle entity object. Notice how we are hard-coding the Status field to the Enum value FOR_SALE. Basically, when the Vehicle is first created it should always have status as FOR_SALE. We didn’t expose this field in the VehicleCreateDTO because we don’t want the consumer accidentally setting this to some unacceptable value.
  • Next, we implement the updateVehicle() method. This method takes the id of the vehicle and the VehicleUpdateDTO object. To do so, we first check if we have a vehicle with the given id. If we find such a vehicle, we update the existing make and model values of the object to the new values. Then, we save the object again using VehicleRepository’s save() method.

Creating the REST Controller for Upserting Data

The last missing piece in our application is the REST Controller to handle the incoming POST and PUT request. We will create a separate class to handle the two commands.

@RestController
@RequestMapping(value = "/api/vehicles")
public class VehicleCommandController {

    @Autowired
    private VehicleCommandService vehicleCommandService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<UUID> createVehicle(@RequestBody VehicleCreateDTO vehicleCreateDTO){
        return new ResponseEntity<>(vehicleCommandService.createVehicle(vehicleCreateDTO), HttpStatus.CREATED);
    }

    @PutMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.OK)
    public ResponseEntity<VehicleQueryDTO> updateVehicle(@PathVariable(value = "id") UUID id,
                                                         @RequestBody VehicleUpdateDTO vehicleUpdateDTO){
        return new ResponseEntity<>(vehicleCommandService.updateVehicle(id, vehicleUpdateDTO), HttpStatus.OK);
    }
}

Nothing drastically different here as compared to the query controller we created in the previous part for querying data.

The main difference is that this time we use @PostMapping and @PutMapping. In the case of POST, we pass the request body to the VehicleCreateDTO object. This is done using the @RequestBody annotation. Then, we use the vehicleCommandService to create the Vehicle and return the response in a Response Entity object.

Similarly, in the case of PUT, we pass the request body to VehicleUpdateDTO object. PUT also receives the id of the resource in the path variable. We can access it using @PathVariable and pass it to the service method.

Another important thing to note is the Response Status. For POST, we are using HttpStatus.CREATED. This, in turn, maps to the response code 201. For the PUT method, we use HttpStatus.OK that maps to response code 200.

And basically, that’s all there is. We should now be able to test our application.

Testing POST and PUT end-points

We were able to test the GET implementation in the last post directly from the browser. However, we can’t do the same for POST and PUT unless we use some special browser plugin like RESTED. This is because POST and PUT method have Request Body in their payload. Browser can’t handle it directly.

However, instead of using a plugin, we will use Postman. It is an industry-standard tool for testing APIs. You can get it from here.

Triggering a REST API from Postman is pretty straightforward. We provide the URL and select the HTTP method from a dropdown. Then, we will provide the body in JSON format.

See below screenshot for the POST method on our Vehicle resource. Note that we have provided the input in JSON format and selected JSON as the input type.

Click the Send button to send the request. If everything has been correct till this point, we will see the response as below. In our case it will just be the UUID of the vehicle resource. Also, note the HTTP Status of 201 (Created).

Now, we try to update the Vehicle we just inserted. See below for the required setup in Postman.

Here, we are providing the make and model. In the URL path we provide the UUID that we received as output to the POST call. We also selected PUT from the dropdown.

After we send the request, we receive the response JSON. In this case, we are returning all the fields. We can see that the model field has been successfully updated.

Conclusion

With the above testing completed, we have successfully created REST API using Spring Boot with GET, POST and PUT end-points. In other words, we have exposed four REST API endpoints to our Vehicle entity.

However, we are still far from a really robust implementation. For instance, we don’t have exception handling and validations. We also don’t have proper documentation of our API end-points.

However, nothing to worry. We will be looking at those aspects in upcoming posts.

In the next Exception Handling in a Spring Boot application, we will enhance our application to handle exceptions. This will make it more robust and closer to production-level.

Happy Learning!

Download Source Code

$ git clone https://github.com/favtuts/java-spring-boot-microservices/tree/spring-boot-rest-api
$ cd java-spring-boot-microservices/sources/spring-boot-starter-app
$ mvn clean package spring-boot:run

Leave a Reply

Your email address will not be published. Required fields are marked *