DEV Community

Cover image for Getting started with Spring Boot: Creating a simple movies list API.
Elattar Saad
Elattar Saad

Posted on

Getting started with Spring Boot: Creating a simple movies list API.

Spring boot is widely considered an entry to the Spring ecosystem, simple yet effective and powerful! Spring boot makes it possible to create Spring-based stand-alone, quality production apps with minimum needed configuration specifications. Let's keep it simple, just like the movies list API we're about to create!

Before we start

Your project structure matters, Spring boot gives you all the freedom to structure your project the way you see fit, you may want to use that wisely.

There's not a particular structure you need to follow, I'll be introducing mine on this project but that doesn't mean you have to follow it the way it is, as I said you're free!

Most tech companies, including mine, use guiding dev rules to keep the code clean and pretty, the rules are there to help you, don't go rogue.

Phase 0 : Introduction to the global architecture

Before we get busy with the layers specifications, let's have a bird's-eye view :

Phase I : Model definition

Models are the data structure which you're project is dealing with (example: you're building a user management system, the user is your model among others).

Usually, the software architect will be there to provide you with the needed resources to build your Spring boot models, those resources mainly are UML class diagrams. Here's ours :

NOTE: I decided for this model layer to be as simple as possible, to not give a wall to climb instead of a first step. In real-life projects, things get ugly and much more complicated.

Phase II : Service definition

After the model layer, now it's time to blueprint our services' structure, in other words, we need to define what service our API is going to mainly provide.

The services usually are fully invocated by the controller layer which means they usually have the same structure, but not all the time.

This one also will be provided by the software architect, and it comes as a detailed list with descriptions, a set of UML sequence diagrams, or both.

We need our API to be able to provide us with a list of saved movies, save, update and delete a movie.

Our end-points will be the following:

  • Save EP: This allows saving a movie by sending a JSON object using an HTTP/POST request.
  • Update EP: This allows updating a movie by sending a JSON object using an HTTP/PUT request.
  • Delete EP: This allows deleting a movie by sending its id using an HTTP/DELETE request.
  • Find all EP: This allows retrieving all the existing movies by sending an HTTP/GET request. (It's not recommended to open a find all end-point because it will slow down your application, instead set a limit, Example: 10 movies per request).

Why are we using multiple HTTP verbs?

If I were in your shoes right now, I'd be asking the same question. First, you can use it as you want with no restrictions, use an HTTP/POST request to retrieve movies, yes you can do that but, there's a reason why we don’t.

Dev rules and conventions are there to help you and keep things in order, otherwise, all we'll be building is chaos!

HTTP verbs are meant to describe the purpose of the request:

HTTP Verb CRUD Op Purpose On success On failure
POST Create Creates a resource 201 404 | 409
GET Read Retrieves a resource 200 404
PUT Update Updates (replaces) a resource 200 | 204 404 | 405
PATCH Update Updates (modifies) a resource 200 | 204 404 | 405
DELETE Delete Deletes a resource 200 404 | 405

Each request requires a response, in both success and error scenarios, HTTP response codes are there to define what type of response we are getting.

HTTP response code Meaning
100 - 199 Informational responses
200 - 299 Successful responses
300 - 399 Redirection responses
400 - 499 Client error responses
500 - 599 Server error responses

Know more about HTTP response codes here

Phase III: Technical benchmarking

Now, we need to define the project technical requirements, what communication protocol we should use, what Database is suitable to store data, etc.

We will be using REST as our main communication protocol, H2/file database to store our data.
I chose those technologies for instructional purposes so that I could offer a simple yet working example, but in real-life projects, we select technologies only on the basis of their utility and ability to aid in the development of our API. That was the second factor that influenced my decision.

NOTE: The more technology experience you have, the more accurate your decisions will be.

Phase IV: Development

Github repository initialization

To make this code accessible for all of you, and insure its version, we'll be using git and GitHub, find all the code here : Github Repo

Spring Initializr

Spring provides us with an Initialization suite that helps us start our project quicker than usual.

After the init and downloading the basic version of our project, there're three important files you need to know about :

1. The application main class (aka. the backbone)

This class is the main class of the project and it represents its center.
NOTE: All packages should be under the main package of your application so they can be bootstrapped.

package io.xrio.movies;

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

@SpringBootApplication
public class 
MoviesApplication {

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

}
Enter fullscreen mode Exit fullscreen mode

2. The application properties file

This file is a way to put your configuration values which the app will use during the execution (example: URL to the database, server's port, etc.).
NOTE: You can add more properties files, I'll be covering that up in an upcoming article.
NOTE1: By default the file is empty.

spring.datasource.url=jdbc:h2:file:./data/moviesDB
spring.jpa.defer-datasource-initialization=true
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
Enter fullscreen mode Exit fullscreen mode

3. The pom file

Since we are using Maven as our dependency manager (you can use Gradle instead) we have a pom.xml file containing all the data about our dependencies.

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.xrio</groupId>
    <artifactId>movies</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>movies</name>
    <description>Just a simple movies api</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Enter fullscreen mode Exit fullscreen mode

Building the model layer

The model layer is the direct implementation of our class diagrams as Java classes.

package io.xrio.movies.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;

@Data
@Entity
public class Movie {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String name;
    private String type;
    private Long duration;
    private Long releaseYear;

}
Enter fullscreen mode Exit fullscreen mode

The


 provides us with embedded constructors and accessors so we can reduce the written code. see more about the [Lombok project](https://projectlombok.org/).

The

 ```@Entity```

 is there to tell Spring Data that particular class should be represented as a table in our relational database. we call that process an ORM (Object Relational Mapping).

So that our id won’t be duplicated, we will use a sequence, we put the

 ```@GeneratedValue(strategy = GenerationType.SEQUENCE)```

 there.

### Building the repository facade
Simply, an interface that will inherit Spring Data JPA powers and handle the CRUD ORM operations on our behalf.



Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.repository;

import io.xrio.movies.model.Movie;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MoviesRepository extends JpaRepository {
}




@Repository ensures the dependency injection of the repository bean among other things, since the interface inherits from the generic JpaRepository, it will have all the already-set mechanisms of Spring Data JPA.

Spring Data is smart enough at the level it can handle a new operation only from the function signature. Example: You need to file a movie by its title? No problem, just add this function to your interface :



Enter fullscreen mode Exit fullscreen mode

Movie findByName(String name);




### Building the custom exception

Before building our service layer, we need a couple of custom exceptions that will be thrown when things go south.

Basing on our service schema only two exceptions can be thrown when:
- Creating an already existing movie (MovieDuplicatedException class).



Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.exception;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**

  • @author : Elattar Saad
  • @version 1.0
  • @since 10/9/2021
    */
    @EqualsAndHashCode(callSuper = true)
    @Data
    @AllArgsConstructor
    public class MovieDuplicatedException extends Exception{

    /**

    • The duplicated movie's id */ private Long mid;

}




- Updating a non-existing movie (MovieNotFoundException class).



Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.exception;

import lombok.Data;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
public class MovieNotFoundException extends Exception{

/**
 * The nonexistent movie's id
 */
private Long mid;
Enter fullscreen mode Exit fullscreen mode

}



***NOTE: There're mainly four ways to handle exception in Spring boot.***

***NOTE1: You can generalize custom exceptions so they can be used for more than one model.***

***NOTE2: You can handle your exceptions without custom-made exceptions and handlers.***


### Building the service layer
First, we will create a movie service interface that will bear the schema of our service, then implement it with the needed logic. This helps us to achieve the [purpose of the IoC](https://en.wikipedia.org/wiki/Inversion_of_control).



Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.service;

import io.xrio.movies.exception.MovieDuplicatedException;
import io.xrio.movies.exception.MovieNotFoundException;
import io.xrio.movies.model.Movie;

import java.util.List;

public interface MovieService {

Movie save(Movie movie) throws MovieDuplicatedException;
Movie update(Movie movie) throws MovieNotFoundException;
Long delete(Long id) throws MovieNotFoundException;
List<Movie> findAll();
Enter fullscreen mode Exit fullscreen mode

}






Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.service.impl;

import io.xrio.movies.exception.MovieDuplicatedException;
import io.xrio.movies.exception.MovieNotFoundException;
import io.xrio.movies.model.Movie;
import io.xrio.movies.repository.MoviesRepository;
import io.xrio.movies.service.MoviesService;
import lombok.Data;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Data
public class MovieServiceImpl implements MovieService {

final MovieRepository movieRepository;

@Override
public Movie save(Movie movie) throws MovieDuplicatedException {
    Movie movieFromDB = movieRepository.findById(movie.getId()).orElse(null);
    if (movieFromDB != null)
        throw new MovieDuplicatedException(movie.getId());
    return movieRepository.save(movie);
}

@Override
public Movie update(Movie movie) throws MovieNotFoundException {
    Movie movieFromDB = movieRepository.findById(movie.getId()).orElse(null);
    if (movieFromDB == null)
        throw new MovieNotFoundException(movie.getId());
    movie.setId(movieFromDB.getId());
    return movieRepository.save(movie);
}

@Override
public Long delete(Long id) throws MovieNotFoundException {
    Movie movieFromDB = movieRepository.findById(id).orElse(null);
    if (movieFromDB == null)
        throw new MovieNotFoundException(id);
    movieRepository.delete(movieFromDB);
    return id;
}

@Override
public List<Movie> findAll() {
    return movieRepository.findAll();
}
Enter fullscreen mode Exit fullscreen mode

}




I combined the use of constructor dependency injection and Lombok’s

 ```@RequiredArgsConstructor```

 to reduce my code, without that it will look like this :



Enter fullscreen mode Exit fullscreen mode

@Service
public class MovieServiceImpl implements MovieService {

final MovieRepository movieRepository;

public MovieServiceImpl(MovieRepository movieRepository) {
    this.movieRepository = movieRepository;
}
Enter fullscreen mode Exit fullscreen mode

...
}




### Building the controller layer

After the service layer, time to build our controller to accept incoming requests.

As previously said, the controller layer directly calls the service layer, that's the reason behind the injection of a MovieService bean inside the movie controller.



Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.controller;

import io.xrio.movies.exception.MovieDuplicatedException;
import io.xrio.movies.exception.MovieNotFoundException;
import io.xrio.movies.model.Movie;
import io.xrio.movies.service.MovieService;
import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("movie")
@Data
public class MovieController {

final MovieService movieService;

@PostMapping("/")
public ResponseEntity<?> save(@RequestBody Movie movie) throws MovieDuplicatedException {
    if (movie == null)
        return ResponseEntity.badRequest().body("The provided movie is not valid");
    return ResponseEntity.status(HttpStatus.CREATED).body(movieService.save(movie));
}

@PutMapping("/")
public ResponseEntity<?> update(@RequestBody Movie movie) throws MovieNotFoundException {
    if (movie == null)
        return ResponseEntity.badRequest().body("The provided movie is not valid");
    return ResponseEntity.ok().body(movieService.update(movie));
}

@DeleteMapping("/{id}")
public ResponseEntity<?> delete(@PathVariable Long id) throws MovieNotFoundException {
    if (id == null)
        return ResponseEntity.badRequest().body("The provided movie's id is not valid");
    return ResponseEntity.ok().body("Movie [" + movieService.delete(id) + "] deleted successfully.");
}

@GetMapping("/")
public ResponseEntity<?> findAll() {
    return ResponseEntity.ok().body(movieService.findAll());
}
Enter fullscreen mode Exit fullscreen mode

}




One more last step and we're good to go. To handle the exception thrown from the service layer, we need to catch it in the controller layer.

Fortunately, Spring boot provides us with an Exception Handler that can resolve exceptions under the hood and without any code to the controller.



Enter fullscreen mode Exit fullscreen mode

package io.xrio.movies.controller.advice;

import io.xrio.movies.exception.MovieDuplicatedException;
import io.xrio.movies.exception.MovieNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class MovieControllerExceptionHandler {

@ExceptionHandler(MovieNotFoundException.class)
private ResponseEntity<?> handleMovieNotFoundException(MovieNotFoundException exception){
    String responseMessage = "The provided movie ["+exception.getMid()+"] is nowhere to be found.";
    return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(responseMessage);
}

@ExceptionHandler(MovieDuplicatedException.class)
private ResponseEntity<?> handleMovieDuplicatedException(MovieDuplicatedException exception){
    String responseMessage = "The provided movie ["+exception.getMid()+"] is already existing.";
    return ResponseEntity
            .status(HttpStatus.CONFLICT)
            .body(responseMessage);
}
Enter fullscreen mode Exit fullscreen mode

}




All we need to do next is to test what we build via a rest client, for this reason, I'm using [Insomnia](https://insomnia.rest/) :

##### The post end-point

<p align="center">
  <img src="https://elattar.me/images/spring/movie-test-post.png">
</p>

##### The get end-point

<p align="center">
  <img src="https://elattar.me/images/spring/movie-test-get.png">
</p>

##### The put end-point

<p align="center">
  <img src="https://elattar.me/images/spring/movie-test-put.png">
</p>

##### The delete end-point

<p align="center">
  <img src="https://elattar.me/images/spring/movie-test-delete.png">
</p>

And finally, testing the error scenario for some end-points.

Let's try and save a movie with an existing id :
<p align="center">
  <img src="https://elattar.me/images/spring/movie-test-save-error.png">
</p>

Or delete a non-existing movie :
<p align="center">
  <img src="https://elattar.me/images/spring/movie-test-delete-error.png">
</p>

## Summary 

Spring Boot is a powerful apps making tool, takes away all the times consuming tasks and leaves you with the logical ones to handle.
Don't get fouled, building APIs is not that easy, and from what I saw, that's just the tip of the Iceberg!

Find the source code [Here](https://github.com/xrio/simple-spring-boot-movies). 

More articles [Here](https://elattar.me/).
Enter fullscreen mode Exit fullscreen mode

Discussion (0)