Set Up Your Development Environment
Before you begin, make sure you have the following installed on your system:
Java Development Kit (JDK) 20 or later
An IDE of your choice (e.g., IntelliJ IDEA, Eclipse)
Generate a Spring Boot Project with Spring Initializr
Spring Initializr is a web-based tool that helps you quickly bootstrap Spring Boot projects. Follow these steps to quickly generate one:
Open your web browser and go to https://start.spring.io/.
Select the following options:
Project: Gradle Project
Language: Java
Spring Boot: Choose the desired version (e.g., 3.1.0)
Group: com.codaholic
(replace with your desired package name)
Artifact: shop (replace with your desired project name)
Packaging: Jar
Java: Choose the desired version (17)
Dependencies: Select the following: Spring Web, Lombok, H2 Database, Spring Boot Data JPA, Spring Boot DevTools, and Spring Boot Configuration Processor
Click on the "Generate" button to download the project as a ZIP file.
Import the Project into Your IDE
Unzip the downloaded ZIP file to a directory of your choice.
Open your IDE and import the project as a Gradle project. If you are using IntelliJ IDEA, go to File -> New -> Project from Existing Sources and select the project's directory. Follow the instructions to complete the import process.
Update build.gradle: Open up build.gradle
and ensure that sourceCompatibility
and targetCompatibility
are 17
java {
sourceCompatibility = '17'
targetCompatibility = '17'
project.buildDir = 'target'
}
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
Create A Product
Model
Open the generated project in your IDE.
Create a directory for models src/main/java/com/codaholic/shop/models
Create a new java class in the models
directory and name it Product.java
, and add the following piece of code
package com.codaholic.shop.models;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.*;
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "product")
@EqualsAndHashCode(callSuper = false)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long Id;
@NotEmpty(message = "Name cannot be blank")
@Column(unique = true)
private String name;
private long quantity;
private double unitPrice;
}
Let me explain what the piece of code above is doing
package com.codaholic.shop.models;
This line shows the package to which the Product
class belongs. Packages are used to group related classes together.
The @Entity
annotation indicates that this class represents a persistent entity in the database. It is typically used with JPA to map the class to a database table.
The @Data
and @EqualsAndHashCode
annotations are provided by Lombok, a library that reduces boilerplate code. @Data
generates getters, setters, toString()
, and equals()
and hashCode()
methods. @EqualsAndHashCode(callSuper = false)
tells Lombok to generate equals()
and hashCode()
methods based only on the fields in this class and not in any superclass.
@Builder
is another Lombok annotation that generates a builder pattern for the class, allowing the convenient creation of instances.
@NoArgsConstructor
and @AllArgsConstructor
are Lombok annotations that generate constructors with no arguments and constructors with all arguments, respectively.
@Table(name = "product")
specifies the name of the database table associated with this entity. In this case, the table name is "product".
@Id
indicates that the id
field is the primary key of the entity.
@GeneratedValue(strategy = GenerationType.IDENTITY)
: It's a JPA annotation that defines the strategy for generating the primary key values. In this case, the IDENTITY
strategy indicates that the primary key values will be automatically generated by the database.
If you don’t add this annotation, the id
field will not be incremented by the database, hence, when you want to insert another record you’ll be greeted with a unique key constraint.
@NotEmpty
checks if a value is not null and not empty.
The private
access modifier restricts direct access to these fields, and the appropriate getter and setter methods will be generated by Lombok's @Data
annotation.
name
: Represents the name of the product.
quantity
: Represents the quantity of the product.
unitPrice
: Represents the unit price of the product.
Create A ProductRepository
Create a directory for repositories src/main/java/com/codaholic/shop/repository
Create a new Java class, `ProductRepository.java`
, and add the following piece of code
package com.codaholic.shop.repository;
import com.codaholic.shop.models.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
The ProductRepository
interface extends JpaRepository<Product, Long>
What this means is that ProductRepository
inherits the methods that are defined in JpaRepository
specific to the Product
entity.
The inherited `JpaRepository`
interface contains methods such as save
, findById
, findAll
, delete
, etc., which are suited for carrying out common database operations on the Product
entity.
The Long
type argument represents the data type of the primary key of the Product
entity.
Create A ProductService
Create a directory for services src/main/java/com/codaholic/shop/services
Create a new Java class in the directory you just created `ProductService.java`
, and add the following piece of code
package com.codaholic.shop.services;
import com.codaholic.shop.models.Product;
import com.codaholic.shop.repository.ProductRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
private final ProductRepository productRepository;
ProductService(ProductRepository productRepository){
this.productRepository = productRepository;
}
public List<Product> getAllProducts() {
return productRepository.findAll();
}
public Product createProduct(Product product) {
return productRepository.save(product);
}
public Product getProductById(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Product not found"));
}
public Product updateProduct(Long id, Product productDetail) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Product not found"));
product.setName(productDetail.getName());
product.setQuantity(productDetail.getQuantity());
product.setUnitPrice(productDetail.getUnitPrice());
return productRepository.save(product);
}
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
}
Below is an explanation of what the piece of code above does
The following methods define the operations or actions that can be performed on products.
getAllProducts
: Gets all products by calling `productRepository.findAll()`
, which queries the database and comes back with a list of products.
createProduct
: This adds a new product by calling productRepository.save()
with the given product object, which saves the product to the database and comes back with the saved product.
getProductById
: This fetches a specific product by its ID by calling productRepository.findById()
with the given ID. If the product is found, it is returned. Else, it throws `IllegalArgumentException`
a "Product not found" message.
updateProduct
: This updates a specific product identified by its ID. What it does first is to get the existing product from the database using productRepository.findById()
. If the said product is found, it will update its name and price with the details contained in the productDetail
object. Finally, it saves the updated product using productRepository.save()
and returns the updated product.
deleteProduct
: This deletes a specific product identified by its ID by invoking the`productRepository.deleteById()`
, which deletes the product from the database.
From the above, you can deduce that ProductService
abstracts the business logic related to products and interacts with the ProductRepository
data access operations. This allows for the separation of concerns, modularity, testability, and maintainability in the Spring Boot Web API architecture.
Create A ProductController
Create a directory for controllers src/main/java/com/codaholic/shop/controllers
Create a new Java class, ProductController.java
, and add the following piece of code
package com.codaholic.shop.controllers;
import com.codaholic.shop.models.Product;
import com.codaholic.shop.services.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/product")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
List<Product> products = productService.getAllProducts();
return ResponseEntity.ok(products);
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product createdProduct = productService.createProduct(product);
return ResponseEntity.status(HttpStatus.CREATED).body(createdProduct);
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
Product updatedProduct = productService.updateProduct(id, productDetails);
return ResponseEntity.ok(updatedProduct);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.noContent().build();
}
}
Below is an explanation of what **ProductController**
does:
Starting with the methods annotated with @GetMapping
, @PostMapping
, @PutMapping
, and @DeleteMapping
. These annotations specify the corresponding HTTP methods (GET
, POST
, PUT
, DELETE
) for the API endpoints.
getAllProducts
: This method sends all the requests for getting all products to the productService.getAllProducts()
and returns them in the response body.
createProduct
: This method directs all requests for creating a new product by calling productService.createProduct()
with the provided product object in the request body. It returns the created product in the response body with a status code of 201 (CREATED).
getProductById
: This method directs all requests for getting a specific product by its ID by calling productService.getProductById()
with the provided ID. It returns the product in the response body.
updateProduct
: This method directs all requests for updating a specific product identified by its ID. It calls productService.updateProduct()
with the provided ID and product details in the request body. It returns the updated product in the response body.
deleteProduct
: This method directs all requests for deleting a specific product identified by its ID by calling productService.deleteProduct()
. It returns a response with a status code of 204 (NO CONTENT), indicating a successful deletion with no response body.
From the piece of code above, you can see that the ProductController
acts as a manager, they don’t contain any business logic, they just route the request to the corresponding methods in ProductService
Set up the H2 Database
For the purpose of demonstration, we will use a temporary database. Below is the configuration to set up the database.
Rename application.properties
in src/main/resources/
to application.yml
Next, add the below Yaml configuration to src/main/resources/application.yml
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.H2Dialect
show-sql: true
hibernate:
ddl-auto: create-drop
server:
port: 3000
compression:
enabled: true
error:
include-message: always
shutdown: "graceful"
Build and Run the Application
Open a terminal or command prompt.
Navigate to the project's root directory.
Run the following command to build the project:
./gradlew bootRun
The Spring Boot application will start, and you will see log messages showing that the server is up and running.
Let’s test our Spring Boot Web API
To test our API, we need to download and install a REST Client called Postman. A REST Client is an application that API developers use to test their Web APIs.
NB: You can use any REST Client of your choice, it must not be Postman
First, we need to create a resource, we need to add a product into our database. To do that we need to create a POST
request to this endpoint http://localhost:3000/api/product
in JSON format as shown in the image below.
To Fetch all the products that have been added or created, send a GET request to http://localhost:3000/api/product as shown in the image below.
To Fetch just one product or a particular product, send a GET request with the specific ID to http://localhost:3000/api/product/1 as shown in the image below.
To update any product, send a PUT request with the specific ID to http://localhost:3000/api/product/1 as shown in the image below.
To delete a product, send a DELETE request with the specific ID to http://localhost:3000/api/product/1 as shown in the image below:
Congratulations, our Spring Boot Web API is working as intended.