Creating a CRUD REST API with Spring Boot and PostgreSQL: A Step-by-Step Guide ๐Ÿชœ

Creating a CRUD REST API with Spring Boot and PostgreSQL
Photo by Obi - @pixel8propix / Unsplash

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)


Project Setup

Spring Boot Initialization

  1. Generate Project: Use Spring Initializr.
  2. Select Project Details: Gradle Project, Java, latest Spring Boot version, Jar packaging, and Java 17.
  3. Dependencies: Spring Web, Spring Data JPA, PostgreSQL Driver.
  4. Download and Import: Generate, extract, and import the project into your IDE as a Gradle project.

PostgreSQL Configuration

  1. Install PostgreSQL: Ensure it's installed and running.
  2. Create Database: Create a productdb database.
  3. Configure

Folder Structure

Our final folder structure will be something like this

โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ main/
โ”‚   โ”‚   โ”œโ”€โ”€ java/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ net/
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ camelcodes/
โ”‚   โ”‚   โ”‚           โ”œโ”€โ”€ 
โ”‚   โ”‚   โ”‚           โ”œโ”€โ”€ controller/
โ”‚   โ”‚   โ”‚           โ”‚   โ””โ”€โ”€
โ”‚   โ”‚   โ”‚           โ”œโ”€โ”€ model/
โ”‚   โ”‚   โ”‚           โ”‚   โ””โ”€โ”€
โ”‚   โ”‚   โ”‚           โ”œโ”€โ”€ repository/
โ”‚   โ”‚   โ”‚           โ”‚   โ””โ”€โ”€
โ”‚   โ”‚   โ”‚           โ””โ”€โ”€ service/
โ”‚   โ”‚   โ”‚               โ””โ”€โ”€
โ”‚   โ”‚   โ””โ”€โ”€ resources/
โ”‚   โ”‚       โ””โ”€โ”€
โ”‚   โ””โ”€โ”€ test/
โ”œโ”€โ”€ build.gradle
โ””โ”€โ”€ settings.gradle


Create in net/camelcodes/model:

package net.camelcodes.model;

import javax.persistence.*;

@Table(name = "products")
public class Product {
    @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

package net.camelcodes.repository;

import net.camelcodes.model.Product;

public interface ProductRepository extends JpaRepository<Product, Long> {

Implement the Service Layer

Create 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;

public class ProductService {

    private ProductRepository productRepository;

    public Product createProduct(Product 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 -> {

    public boolean deleteProduct(Long productId) {
        return productRepository.findById(productId)
                .map(product -> {
                    return true;

Implement the Controller

Modify 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;

public class ProductController {

    private ProductService productService;

    public Product createProduct(@RequestBody Product product) {
        return productService.createProduct(product);

    public List<Product> getAllProducts() {
        return productService.getAllProducts();

    public ResponseEntity<Product> getProductById(@PathVariable(value = "id") Long productId) {
        return productService.getProductById(productId)
                .orElseGet(() -> ResponseEntity.notFound().build());

    public ResponseEntity<Product> updateProduct(@PathVariable(value = "id") Long productId,
                                                 @RequestBody Product productDetails) {
        return productService.updateProduct(productId, productDetails)
                .orElseGet(() -> ResponseEntity.notFound().build());

    public ResponseEntity<Void> deleteProduct(@PathVariable(value = "id") Long productId) {
        return productService.deleteProduct(productId) ?
               ResponseEntity.ok().build() :

Running and Testing

  1. Start the Application: Run it via your IDE or ./gradlew bootRun.
  2. Test Endpoints: Use tools like Postman to test the API.

Happy Coding ๐Ÿงก