Java Spring

Spring REST Web Services – CRUD API for Products

Spring REST Web Services – CRUD API for Products

REST (Representational State Transfer) web services are the backbone of modern web and mobile applications. Spring Boot makes building REST APIs straightforward with its @RestController, @RequestMapping, and Spring Data JPA. This tutorial builds a complete RESTful CRUD API for a product catalog — the kind of API that a mobile app or front-end framework like Angular or React would consume.

What You Will Build

HTTP Method URL Operation
GET /api/products Get all products
GET /api/products/{id} Get product by ID
POST /api/products Add new product
PUT /api/products/{id} Update product
DELETE /api/products/{id} Delete product

Maven Dependencies (Spring Boot)


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

application.properties


spring.datasource.url=jdbc:mysql://localhost:3306/java9rdb?useSSL=false
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

Product.java – JPA Entity


package com.java9r.model;

import javax.persistence.*;
import javax.validation.constraints.*;

@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Column(nullable = false, length = 100)
    private String name;

    @Positive
    private double price;

    @Min(0)
    private int quantity;

    public Product() {}

    public Product(String name, double price, int quantity) {
        this.name     = name;
        this.price    = price;
        this.quantity = quantity;
    }

    // Getters and setters
    public Long   getId()          { return id; }
    public String getName()        { return name; }
    public double getPrice()       { return price; }
    public int    getQuantity()    { return quantity; }
    public void   setId(Long id)   { this.id = id; }
    public void   setName(String n){ this.name = n; }
    public void   setPrice(double p){ this.price = p; }
    public void   setQuantity(int q){ this.quantity = q; }
}

ProductRepository.java – Spring Data JPA


package com.java9r.repository;

import com.java9r.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    // Spring Data JPA auto-generates CRUD methods
    // Custom query methods using method name conventions:
    List<Product> findByNameContainingIgnoreCase(String keyword);
    List<Product> findByPriceLessThan(double maxPrice);
    List<Product> findByOrderByPriceAsc();
}

ProductController.java – REST Controller


package com.java9r.controller;

import com.java9r.model.Product;
import com.java9r.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "*")  // allow requests from any origin (Angular, React, etc.)
public class ProductController {

    @Autowired
    private ProductRepository repo;

    // GET /api/products – list all
    @GetMapping
    public List<Product> getAllProducts() {
        return repo.findAll();
    }

    // GET /api/products/{id} – find one
    @GetMapping("/{id}")
    public ResponseEntity<Product> getById(@PathVariable Long id) {
        return repo.findById(id)
                .map(p -> ResponseEntity.ok(p))
                .orElse(ResponseEntity.notFound().build());
    }

    // POST /api/products – create
    @PostMapping
    public ResponseEntity<Product> create(@Valid @RequestBody Product product) {
        Product saved = repo.save(product);
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);
    }

    // PUT /api/products/{id} – update
    @PutMapping("/{id}")
    public ResponseEntity<Product> update(@PathVariable Long id,
                                          @Valid @RequestBody Product updated) {
        return repo.findById(id).map(existing -> {
            existing.setName(updated.getName());
            existing.setPrice(updated.getPrice());
            existing.setQuantity(updated.getQuantity());
            return ResponseEntity.ok(repo.save(existing));
        }).orElse(ResponseEntity.notFound().build());
    }

    // DELETE /api/products/{id}
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        if (repo.existsById(id)) {
            repo.deleteById(id);
            return ResponseEntity.noContent().build();  // 204 No Content
        }
        return ResponseEntity.notFound().build();       // 404 Not Found
    }

    // GET /api/products/search?keyword=laptop
    @GetMapping("/search")
    public List<Product> search(@RequestParam String keyword) {
        return repo.findByNameContainingIgnoreCase(keyword);
    }

    // GET /api/products/cheap?maxPrice=10000
    @GetMapping("/cheap")
    public List<Product> findCheap(@RequestParam double maxPrice) {
        return repo.findByPriceLessThan(maxPrice);
    }
}

Testing the API with curl


# Get all products
curl http://localhost:8080/api/products

# Get one product
curl http://localhost:8080/api/products/1

# Create a product
curl -X POST http://localhost:8080/api/products \
     -H "Content-Type: application/json" \
     -d '{"name":"Laptop","price":75000,"quantity":10}'

# Update a product
curl -X PUT http://localhost:8080/api/products/1 \
     -H "Content-Type: application/json" \
     -d '{"name":"Laptop Pro","price":85000,"quantity":8}'

# Delete a product
curl -X DELETE http://localhost:8080/api/products/3

# Search
curl "http://localhost:8080/api/products/search?keyword=laptop"

Sample JSON Responses


// GET /api/products
[
  {"id":1,"name":"Laptop","price":75000.0,"quantity":10},
  {"id":2,"name":"Smartphone","price":25000.0,"quantity":50},
  {"id":3,"name":"Tablet","price":35000.0,"quantity":30}
]

// POST /api/products (201 Created)
{"id":4,"name":"Monitor","price":15000.0,"quantity":20}

// DELETE /api/products/3 (204 No Content – empty body)

// GET /api/products/99 (404 Not Found)

HTTP Status Codes Used

Status Meaning Used For
200 OKSuccessGET, PUT
201 CreatedResource createdPOST
204 No ContentSuccess, no bodyDELETE
400 Bad RequestValidation failedInvalid input
404 Not FoundResource not foundGET/PUT/DELETE by ID

Summary

Spring Boot + Spring Data JPA makes building REST APIs remarkably concise. The JpaRepository interface provides all standard CRUD methods automatically, and custom query methods are generated from method names — no SQL needed for most use cases. The @RestController converts return values to JSON automatically. Always return appropriate HTTP status codes (201 for creates, 204 for deletes, 404 for missing resources) and validate input with Bean Validation annotations. This API is ready to be consumed by Angular, React, Vue.js, or mobile applications.

Topics: Java Spring
← Newer Post Older Post →