Building Your First REST API with Spring Boot

This post links to: Building a Spring Boot CRUD App With Postgres from Scratch: The Complete Guide.

This guide follows on from Creating Your First JPA Entity and Repository in Spring Boot. Detailed below is how to create a REST Controller that will interact with our JPA repository.

REST Controllers & APIs

To understand REST controllers and REST APIs, you will want to understand what is RESTful API architecture?

TL;DR: REST is an architectural style for designing APIs (Application Programming Interfaces). APIs in general are interfaces with defined rules between services to allow communication and transfer of data.
In the context of backend webservices, APIs are an entrypoint to access the data from a backend service. The API defines what data you can ask for and how you ask for it.

The MVC (Model-View-Controller) pattern is commonly used to design and develop web applications.

In this guide, we will be focusing on the Model and Controller parts of the MVC pattern.

The basic structure of the layered app will be:
Controller → Service → Repository → Database

Creating the User Service

We can start by creating a simple user service that separates the database logic from the controller logic.
This service will be able to:

// UserService.java
@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(final UserRepository userRepository) {

        this.userRepository = userRepository;
    }

    public List<User> getAllUsers() {

        return userRepository.findAll();
    }

    public User getUserById(final Long id) {

        return userRepository.findById(id)
                             .orElseThrow(() -> new RuntimeException("User not found with ID: " + id));
    }

    public User createUser(final User user) {

        return userRepository.save(user);
    }

    public User updateUser(final Long id, final User updatedUser) {

        final var user = getUserById(id);
        user.setName(updatedUser.getName());
        user.setEmail(updatedUser.getEmail());
        return userRepository.save(user);
    }

    public void deleteUser(final Long id) {

        userRepository.deleteById(id);
    }
}

The @Service annotation will tell Spring to automatically detect this class during component scanning and to create and manage an instance of UserService on start up.
This means you don't need to do final var userService = new UserService(), Spring handles this for you.


@Service
public class UserService {
    // ...
}

The @Service annotation is a specialised version of @Component. It provides semantic meaning that says “this class contains service/business logic”

You can also see an example of constructor-based dependency injection:


@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(final UserRepository userRepository) {

        this.userRepository = userRepository;
    }
    // ...
}

The UserService has a constructor that expects an instance of UserRepository to be passed in.
Spring will detect this, try to find an instance of UserRepository that has already been created and pass it in.
Constructor-based dependency injection is recommended in modern Spring. It allows for loose coupling, easier testing, separation of concerns, reusability and cleaner code.
Spring ensures you only have one managed instance created, rather than multiple unmanaged instances.

Creating a Spring REST Controller

Next, we will create a Spring REST controller to perform CRUD actions on our data.
CRUD is an acronym for the four fundamental operations on data in a database - Create, Read, Update and Delete.
These operations translate into the following HTTP request methods POST (Create), GET (Read), PUT (Update) and DELETE (Delete).
You can view a full list of HTTP request methods here.

// UserController.java
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(final UserService userService) {

        this.userService = userService;
    }

    @GetMapping
    public List<User> getAllUsers() {

        return userService.getAllUsers();
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable("id") final Long id) {

        return userService.getUserById(id);
    }

    @PostMapping
    public User createUser(@RequestBody final User user) {

        return userService.createUser(user);
    }

    @PutMapping("/{id}")
    public User updateUser(@PathVariable("id") final Long id, @RequestBody final User updatedUser) {

        return userService.updateUser(id, updatedUser);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable("id") final Long id) {

        userService.deleteUser(id);
    }
}

Let's break down some of these annotations:

Testing Our REST Controller

Now we have our UserService and UserController set up we can test whether it works or not.

For testing, we're going to use cURL in the terminal.

We can start by creating a user:

curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Jon","email": "jon@example.com"}'

Then we can either get all the stored users:

curl -X GET http://localhost:8080/api/users

Or get a single user by their ID:

curl -X GET http://localhost:8080/api/users/1

We can then update our user:

curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name": "JonUpdated", "email": "jon-updated@example.com"}'

See that our user was updated:

curl -X GET http://localhost:8080/api/users/1

And finally, delete our user:

curl -X DELETE http://localhost:8080/api/users/1

What's Next?

So to recap we:

GitHub Example

Up Next: DTOs and Clean API Design