In my previous article, I wrote about Spring Boot Architecture. I think Spring Boot is great to create REST APIs.
If you're wondering what a REST API is, "REST" stands for * Representational State Transfer*. It's a way to communicate between applications over the HTTP.
This article will show you how to create a REST API with Spring Boot from scratch.
For project development, I've used:
- IntelliJ IDEA 2020+
- Maven 3+
- MySQL Workbench 8+
- Postman v9+
GENERATE THE PROJECT
Go to the website Spring Initializr to initialize the Spring Boot project. Add the following dependencies:
- Spring Boot DevTools: to give us the development tools.
- Lombok: to help us reduce boilerplate code (for example, getters and setters).
- Spring Web: to embed Apache Tomcat and include Spring MVC.
- Spring Data JPA: to facilitate the database layer.
- MySQL Driver: to enable the communication between the Spring Boot application and the database.
Click "Generate" and then import the project onto your IDE as a Maven project.
This is going to be my package structure:
This is my 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 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.4.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.techwithmaddy</groupId>
<artifactId>CustomerAPI</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>CustomerAPI</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</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>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</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>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.1.0</version>
<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>
DATABASE CONFIGURATION
Once you have created the table, let's ensure that the database is set up properly with the application to allow communication between the two.
The script below should go inside the application.properties under src/main/resources.
spring.datasource.url = jdbc:mysql://localhost:3306/customer-management-system
spring.datasource.username = root
spring.datasource.password = Connection
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
server.error.include-stacktrace = never
server.port = 8083
CREATE A MYSQL DATABASE TABLE
I created a database schema called "customer-management-system".
Then, you can run the query below to create a Customer table.
CREATE TABLE Customer (
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(100),
last_name VARCHAR(100),
email VARCHAR(100),
phone_number VARCHAR(255),
PRIMARY KEY (customer)
);
Nice! We're done with all the housekeeping. Now let's jump onto the more interesting part.
CREATE A MODEL CLASS
The model class is a class that represents a real-world object. It's responsible for storing * and *retrieving the data.
Instead, the entity class is a Java class mapped to a database table, and each field corresponds to a database column. I found an interesting discussion on Quora if you'd like to know more about the difference between a model and an entity.
In the example below, we created a Customer model and linked each Java field to the columns in the database.
NOTE:
- The @data is a Lombok annotation that puts together getters, setters, toString method, equals, hashcode, etc.
- The @Column must match the name of the columns in the database.
package com.techwithmaddy.CustomerAPI.model;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "Customer")
@Data
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Integer id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column
private String email;
@Column(name = "phone_number")
private String phoneNumber;
}
CREATE A CUSTOMER REPOSITORY INTERFACE
The repository is an interface responsible for performing database query operations. It extends the JPA Repository, which has CRUD operations built in.
The type arguments are "Customer", the domain class that will be managed, and "Integer", which is the type of id of the domain class.
In this interface, we created a method to find a customer via email.
NOTE: @Query is a Spring Data JPA annotation used to create customized database queries.
package com.techwithmaddy.CustomerAPI.repository;
import com.techwithmaddy.CustomerAPI.model.Customer;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Integer> {
@Query("SELECT c FROM Customer c WHERE c.email =:email")
Customer findCustomerByEmail(String email);
}
CREATE A SERVICE CLASS
The service class is responsible for defining the business logic of our application.
The service class communicates with the repository (and the controller, which we'll build later).
For Dependency Injection, Spring can use auto-wiring. With the annotation @Autowired, Spring will search for a class that matches the property by type, and it will automatically inject the object.
In the service class, we have two methods:
saveCustomer(): this is going to save the customer into the database. The good thing about extending the CRUD repository is that it provides built-in CRUD functions(such as save() ), so we don't need to define them into the service class explicitly.
getCustomerByEmail(): this is going to retrieve the customer using their email. I used the Optional to handle a possible NullPointerException (what if the email we type doesn't exist?).
package com.techwithmaddy.CustomerAPI.service;
import com.techwithmaddy.CustomerAPI.model.Customer;
import com.techwithmaddy.CustomerAPI.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public Customer saveCustomer(Customer savedCustomer) {
Customer customer = new Customer();
customer.setFirstName(savedCustomer.getFirstName());
customer.setLastName(savedCustomer.getLastName());
customer.setEmail(savedCustomer.getEmail());
customer.setPhoneNumber(savedCustomer.getPhoneNumber());
return customerRepository.save(savedCustomer);
}
public Optional<Customer> getCustomerByEmail(String email){
Customer customer = customerRepository.findCustomerByEmail(email);
return Optional.ofNullable(customer);
}
}
CREATE A CUSTOM EXCEPTION
Along with the service class, we also create a CustomerNotFoundException class that we're going to use to throw an Exception in case a customer doesn't exist.
package com.techwithmaddy.CustomerAPI.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class CustomerNotFoundException extends RuntimeException {
public CustomerNotFoundException(){
super("Customer Not Found");
}
}
CREATE A CONTROLLER CLASS
The controller class is responsible for handling the requests coming from the client.
This controller is where we explicitly say that this application is a REST API, thanks to the @RestController annotation. This class will handle the requests.
Then, we use the @RequestMapping, which is the parent annotation of all the other mappings.
In this class, we have two methods:
- saveCustomer(): this method saves the information of the customer into the database. We make use of the POST request to create new data.
We write the POST request like this:
http://localhost:8083/customer/save
- getCustomerByEmail(): this method retrieves data from the database using the email. If the email doesn't exist, then we throw a CustomerNotFoundException.
We write the GET request like this:
http://localhost:8083/customer/retrieve
package com.techwithmaddy.CustomerAPI.api;
import com.techwithmaddy.CustomerAPI.exception.CustomerNotFoundException;
import com.techwithmaddy.CustomerAPI.model.Customer;
import com.techwithmaddy.CustomerAPI.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
@RestController
@RequestMapping(value = "/customer")
public class CustomerController {
@Autowired
private CustomerService customerService;
@RequestMapping(method = {POST}, path = "/save", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Customer saveCustomer(@RequestBody Customer customer){
return customerService.saveCustomer(customer);
}
@RequestMapping(method = {GET}, path = "/retrieve")
@ResponseBody
public Customer getCustomerByEmail(@RequestParam String email){
return customerService.getCustomerByEmail(email).orElseThrow(CustomerNotFoundException::new);
}
}
POSTMAN
Now we can test the application using Postman.
Let's add a couple of customers.
Enter the body of the request:
{
"firstName": "Steve",
"lastName": "Austin",
"email": "steve@austin.com",
"phoneNumber": "01223344556"
}
Hit SEND, and Steve Austin will be saved into the database.
Let's try again with another customer.
{
"firstName": "Linda",
"lastName": "Delgado",
"email": "linda@delgado.com",
"phoneNumber": "01345678999"
}
Linda Delgado is also saved into our database.
On Postman, you should get a 200 OK status response.
Now that we know how to save a customer and have a couple of customers saved in our database, we can make a GET request to retrieve a customer via email.
Let's retrieve the customer, Steve.
And now Linda.
If we try to retrieve a customer that doesn't exist, we get a 404 status code, as we should expect.
Some tips when you create your own application:
Follow the recommended Spring Boot package structure.
Running
mvn clean install
helps clean the dependencies and build the project, in case you remove or rename a resource from the directory.Ensure that you click on the right imports.
-
Ensure that Postman accepts
to avoid ``` 415 Unsupported Media Type ``` .
- Ensure that you set the database schema as
default schema
.
CONCLUSION
In this tutorial, you have learned how to create a Spring Boot REST API.
We have:
- Generated a Spring Boot project.
- Created a database.
- Linked the application to the database.
- Created a RestController, Service and a Repository.
- Created a Custom Exception.
- Used Postman to test the REST API.
In my next article, we will use this application to demonstrate how to write *tests * in Spring Boot.
If you ever want to play with the application, you can clone the Github repository here.
Thanks for reading my article, until next time! 👋🏾