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)

Prerequisites

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

  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 ๐Ÿงก

Sponsorship