Spring Boot CRUD REST API Project Example

Spring Boot has REST support by providing default dependencies right out of the box. Spring Boot provides RestTemplateBuilder that can be used to customize the RestTemplate before calling the REST endpoints. In this article I will show you how to create and run a simple CRUD REST API project using Spring Boot. At the bottom of the page you can also find the link to download the entire project source code.

Prerequisites

  • IntelliJ IDEA (Community edition)
  • Spring Boot 1.4.3.RELEASE
  • Spring 4.3.5.RELEASE
  • Maven 3.1
  • JDK 1.8

Project Structure

Our project represents a simple online store that contain products. We want our application to expose REST API’s CRUD principle – create, read, update, and delete.

  • To Create a product : HTTP POST should be used
  • To Retrieve a product : HTTP GET should be used
  • To Update a product : HTTP PUT should be used
  • To Delete a product : HTTP DELETE should be used

Usually REST Web services return JSON or XML as response, although it is not limited to these types only. Clients can specify (using HTTP Accept header) the resource type they are interested in, and server will return the resource.

Any Spring @RestController in a Spring Boot application will render JSON response by default as long as Jackson2 [jackson-databind] is on the class-path.

If you want to enable XML representation, Jackson XML extension must be present on the class-path. Following dependency should be added to your project:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Note: In order to get XML response instead of JSON, client is expected to send appropriate ‘Accept’ header with value text/xml or application/xml.

Maven Dependency Management (pom.xml)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.codeflex.springboot</groupId>
	<artifactId>SpringbootRestOnlineStore</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>SpringbootRestOnlineStore</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.3.RELEASE</version>
	</parent>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
    		<groupId>com.fasterxml.jackson.dataformat</groupId>
    		<artifactId>jackson-dataformat-xml</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Main Class

package com.codeflex.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages={"com.codeflex.springboot"})
public class SpringbootRestOnlineStore {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootRestOnlineStore.class, args);
	}
}

REST Controller

These are our REST APIs:

  • GET request to /api/product/ returns a list of all products
  • GET request to /api/product/3 returns the product with ID 3
  • POST request to /api/product/ with a JSON product object in the request’s body will create a new product
  • PUT request to /api/product/5 with a JSON product object in the request’s body will update the object with ID 5
  • DELETE request to /api/product/7 deletes the product with ID 7
  • DELETE request to /api/product/ deletes all the products
@RestController
@RequestMapping("/api")
public class RestApiController {

    public static final Logger logger = LoggerFactory.getLogger(RestApiController.class);

    @Autowired
    ProductService productService; //Service which will do all data retrieval/manipulation work

    // -------------------Retrieve All Products--------------------------------------------

    @RequestMapping(value = "/product/", method = RequestMethod.GET)
    public ResponseEntity<List<Product>> listAllProducts() {
        List<Product> products = productService.findAllProducts();
        if (products.isEmpty()) {
            return new ResponseEntity<>(products, HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(products, HttpStatus.OK);
    }

    // -------------------Retrieve Single Product------------------------------------------

    @RequestMapping(value = "/product/{id}", method = RequestMethod.GET)
    public ResponseEntity<?> getProduct(@PathVariable("id") long id) {
        logger.info("Fetching Product with id {}", id);
        Product product = productService.findById(id);
        if (product == null) {
            logger.error("Product with id {} not found.", id);
            return new ResponseEntity<>(new CustomErrorType("Product with id " + id  + " not found"), HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(product, HttpStatus.OK);
    }

    // -------------------Create a Product-------------------------------------------

    @RequestMapping(value = "/product/", method = RequestMethod.POST)
    public ResponseEntity<?> createProduct(@RequestBody Product product) {
        logger.info("Creating Product : {}", product);

        if (productService.isProductExist(product)) {
            logger.error("Unable to create. A Product with name {} already exist", product.getName());
            return new ResponseEntity<>(new CustomErrorType("Unable to create. A Product with name " +
                    product.getName() + " already exist."), HttpStatus.CONFLICT);
        }
        productService.saveProduct(product);

        return new ResponseEntity<>(product, HttpStatus.CREATED);
    }

    // ------------------- Update a Product ------------------------------------------------

    @RequestMapping(value = "/product/{id}", method = RequestMethod.PUT)
    public ResponseEntity<?> updateProduct(@PathVariable("id") long id, @RequestBody Product product) {
        logger.info("Updating Product with id {}", id);

        Product currentProduct = productService.findById(id);

        if (currentProduct == null) {
            logger.error("Unable to update. Product with id {} not found.", id);
            return new ResponseEntity<>(new CustomErrorType("Unable to upate. Product with id " + id + " not found."),
                    HttpStatus.NOT_FOUND);
        }

        currentProduct.setName(product.getName());
        currentProduct.setCategoryId(product.getCategoryId());
        currentProduct.setPrice(product.getPrice());

        productService.updateProduct(currentProduct);
        return new ResponseEntity<>(currentProduct, HttpStatus.OK);
    }

    // ------------------- Delete a Product-----------------------------------------

    @RequestMapping(value = "/product/{id}", method = RequestMethod.DELETE)
    public ResponseEntity<?> deleteProduct(@PathVariable("id") long id) {
        logger.info("Fetching & Deleting Product with id {}", id);

        Product product = productService.findById(id);
        if (product == null) {
            logger.error("Unable to delete. Product with id {} not found.", id);
            return new ResponseEntity<>(new CustomErrorType("Unable to delete. Product with id " + id + " not found."),
                    HttpStatus.NOT_FOUND);
        }
        productService.deleteProductById(id);
        return new ResponseEntity<Product>(HttpStatus.NO_CONTENT);
    }

    // ------------------- Delete All Products-----------------------------

    @RequestMapping(value = "/product/", method = RequestMethod.DELETE)
    public ResponseEntity<Product> deleteAllProducts() {
        logger.info("Deleting All Products");

        productService.deleteAllProducts();
        return new ResponseEntity<Product>(HttpStatus.NO_CONTENT);
    }
}

@RestController: Spring 4′s new @RestController annotation annotation eliminates the need of annotating each method with @ResponseBody.

@RequestBody: Indicates that Spring will bind the incoming HTTP request body to that parameter. While doing that, Spring will use HTTP Message converters to convert the HTTP request body into domain object, based on ACCEPT or Content-Type header present in request.

@ResponseBody: Indictaes that Spring will bind the return value to outgoing HTTP response body. While doing that, Spring will use HTTP Message converters to convert the return value to HTTP response body, based on Content-Type present in request HTTP header. As already mentioned, in Spring 4, you may stop using this annotation.

ResponseEntity: Represents the entire HTTP response.

@PathVariable: This annotation indicates that a method parameter should be bound to a URI template variable.

Testing

Let’s run our project and test it via Postman.

Create product (POST)

As you can see we filled our request body with JSON object and after clicking on “Send” we received 201 Status code and the response body with the same object including the ID.

Update product (PUT)

We updated our pen that we created earlier with another information. It becomes a book now.

Get product (GET)

Delete product (DELETE)

Download project source code

1 COMMENT

  1. i am getting status”: 404,
    “error”: “Not Found”,
    “message”: “No message available”,
    “path”: “/SpringBootOnlineStore/api/product/”
    }
    can u please suggest me how to rectify my error

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.