Creating a CRUD REST API with Spring Boot and PostgreSQL: A Step-by-Step Guide ๐ช
Hello Jhipsters (Java-hipsters) ๐
In this article we are returning to the basics. We'll be creating a Spring Boot application with a CRUD REST API for product management, using Java 17 and PostgreSQL.
We will use Java's Optional
to handle potential null values, enhancing the clarity of our code (you can read more about Optionals in the previous article)
Prerequisites
- Jdk 17 installed
- PostgreSQL Database
- An IDE supporting Java and Gradle
- Basic knowledge of Java, Spring Boot, and REST APIs
Project Setup
Spring Boot Initialization
- Generate Project: Use Spring Initializr.
- Select Project Details: Gradle Project, Java, latest Spring Boot version, Jar packaging, and Java 17.
- Dependencies:
Spring Web
,Spring Data JPA
,PostgreSQL Driver
. - Download and Import: Generate, extract, and import the project into your IDE as a Gradle project.
PostgreSQL Configuration
- Install PostgreSQL: Ensure it's installed and running.
- Create Database: Create a
productdb
database. - Configure
application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/productdb
spring.datasource.username=<db-username>
spring.datasource.password=<db-password>
spring.jpa.hibernate.ddl-auto=update
Folder Structure
Our final folder structure will be something like this
ProductdemoApplication/
โ
โโโ src/
โ โโโ main/
โ โ โโโ java/
โ โ โ โโโ net/
โ โ โ โโโ camelcodes/
โ โ โ โโโ ProductdemoApplication.java
โ โ โ โโโ controller/
โ โ โ โ โโโ ProductController.java
โ โ โ โโโ model/
โ โ โ โ โโโ Product.java
โ โ โ โโโ repository/
โ โ โ โ โโโ ProductRepository.java
โ โ โ โโโ service/
โ โ โ โโโ ProductService.java
โ โ โโโ resources/
โ โ โโโ application.properties
โ โโโ test/
โ
โโโ build.gradle
โโโ settings.gradle
Development
Create Product.java
in net/camelcodes/model
:
package net.camelcodes.model;
import javax.persistence.*;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Double price;
// Getters and Setters
}
Create the Repository
In net/camelcodes/repository
, add ProductRepository.java
:
package net.camelcodes.repository;
import net.camelcodes.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
Implement the Service Layer
Create ProductService.java
in net/camelcodes/service
:
package net.camelcodes.service;
import net.camelcodes.model.Product;
import net.camelcodes.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product createProduct(Product product) {
return productRepository.save(product);
}
public List<Product> getAllProducts()
// FIXME: don't use this in production
// Note that you shouldn't use the findAll
// Always fetch paginated data,
// I'll be writing an article later on this
return productRepository.findAll();
}
public Optional<Product> getProductById(Long productId) {
return productRepository.findById(productId);
}
public Optional<Product> updateProduct(Long productId, Product productDetails) {
return productRepository.findById(productId)
.map(product -> {
product.setName(productDetails.getName());
product.setPrice(productDetails.getPrice());
return productRepository.save(product);
});
}
public boolean deleteProduct(Long productId) {
return productRepository.findById(productId)
.map(product -> {
productRepository.delete(product);
return true;
}).orElse(false);
}
}
Implement the Controller
Modify ProductController.java
in net/camelcodes/controller
:
package net.camelcodes.controller;
import net.camelcodes.model.Product;
import net.camelcodes.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping("/")
public Product createProduct(@RequestBody Product product) {
return productService.createProduct(product);
}
@GetMapping("/")
public List<Product> getAllProducts() {
return productService.getAllProducts();
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable(value = "id") Long productId) {
return productService.getProductById(productId)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable(value = "id") Long productId,
@RequestBody Product productDetails) {
return productService.updateProduct(productId, productDetails)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable(value = "id") Long productId) {
return productService.deleteProduct(productId) ?
ResponseEntity.ok().build() :
ResponseEntity.notFound().build();
}
}
Running and Testing
- Start the Application: Run it via your IDE or
./gradlew bootRun
. - Test Endpoints: Use tools like Postman to test the API.
Happy Coding ๐งก