You are here
Home > java >

How to develop REST CRUD API using Spring Boot ?

REST API Spring BootIt will not be an overstatement if I say that we can’t develop an enterprise application without using webservices as an integration layer. Generally, we develop webservices in the form of either a producer or a consumer or both. However Producer is very important for us because we develop it in Java only. Also, we have full control of database interaction logic implementation with us. Now you might have interpreted the significance of our article ‘How to develop REST CRUD API using Spring Boot ?’. On the other hand, Consumer can be Angular Application, ReactJS Application, Android Device, iOS Device and many others or even our favorite java based RestTemplate(Spring Boot REST Client).

Our focus in this article is on developing producer API(REST API) using Spring Boot. Now let’s discuss on ‘How to develop REST CRUD API using Spring Boot ?’ without leaving any theoretical concept which are very essential to know.

What will you learn from this article?

Once you complete going through all points of this article, You will be able to answer :

1) What is REST and REST API in the context of Webservices?

2) How to create a Spring Boot REST application that incorporates industry level project design ?

3) How to develop CRUD (Create, Retrieve, Update, Delete) operations that can be used by any other even non-java application ?

4) How to write bug free CRUD operations, including exceptions & exception handlers?

5) Equally important, How to use annotations @RestController, @RequestMapping, @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping, @Modifying, @Query, @Transactional, @RestControllerAdvice, @ExceptionHandler, @ControllerAdvice, @ResponseBody, @RequestBody, @PathVariable, @Data, @NoArgsConstructor, @AllArgsConstructor, @Entity, @Component, @Service, @Autowired ?

6) How to work with Spring Boot Data JPA repository interface?

7) How to write modular & reusable code?

8) Moreover, How to implement dynamic code with minimal changes, keeping future change requests in mind?

9) How to develop an integration layer to get interoperability ?

10) How to test REST Application by supplying & receiving JSON data ?

11) Last but not the least you will learn “How to develop REST CRUD API using Spring Boot ?”

What is REST?

REST stands for Representational State Transfer. It transfers state(data) in global format(representational) between two different applications running on different servers. In the process of data transfer, who requests data is called Consumer/Client application and who provides data is called a producer application. REST is an architectural style that follows a set of rules to create webservices. Webservices provide reusable data to multiple applications and interoperability between them on the internet. Web services that conform to the REST architectural style, called RESTful Web services.

How will you define a development of REST API ?

Developing a Rest API is nothing but creating classes & methods in a specific architectural style so that data can be reused between interoperable applications. More or less we create RestController and respective CRUD operations in the process of development of the REST API.

What is RestController ?

In Spring Boot REST programming RestController is a mandatory class which acts as a front controller. It contains several methods that return Body and Status as a ResponseEntity object. Body refers to data in form of String, Object, Collections etc. whereas Status refers to HttpResponse Status(200, 404,405, 500 etc.).

Software used in this Project ?

–STS (Spring Tool Suite) : Version-> 4.7.1.RELEASE
–MySQL Database : Version ->8.0.19 MySQL Community Server
–JDK8 or later versions (Extremely tested on JDK8, JDK9 and JDK14)

Coding Steps

Step#1 : Create Project in STS

If you are new to Spring Boot, visit Internal Link to create a sample project in spring boot. While creating project in STS add 4 starters ‘MySqL Driver’, ‘Spring Data JPA’, ‘Spring Web’ and ‘Lombok’. You can also add ‘Spring Boot DevTools’ optionally. If you are new to ‘Lombok’, kindly visit ‘How to configure Lombok‘ and to know all about it in detail.

Step#2 : Create Database 

You can use database software of your own choice and create a Database accordingly. We have used MySQL database software. Login to your MySQL and execute a Database creation query. In our case DB Name is ‘REST_INVOICE’, so our query will be ‘ CREATE DATABSE REST_INVOICE’.

Step#3 : Update application.properties and Write Classes & methods(REST API) 

We are considering ‘Invoice’ as a model to develop the REST API. Invoice will have many fields such as invoiceNumber, invoiceName, invoiceAmount etc.. Below Table will show the list of classes & other files used in the project accordingly.

Package/Location Class/Interface/file namePurpose
src/main/resourcesapplication.propertiesproperties file to declare common properties in the project
com.dev.invoice.rest.entityInvoice.javaModel/Entity class with Database table mapping
com.dev.invoice.rest.repoInvoiceRepository.javaRepository Interface which extends JpaRepository interface
com.dev.invoice.rest.serviceIInvoiceService.javaService interface with Database related methods
com.dev.invoice.rest.service.implInvoiceServiceImpl.javaService class contains implementations of methods declared in Service interface for Database related operations
com.dev.invoice.rest.entityErrorType.javaHelper class used in InvoiceErrorHandler.java
com.dev.invoice.rest.exceptionInvoiceNotFoundException.javaTo define custom Exception if any Invoice not found
com.dev.invoice.rest.exception.handlerInvoiceErrorHandler.javaTo handle the error In case InvoiceNotFoundException is thrown
from any controller method
com.dev.invoice.rest.utilInvoiceUtil.javaUtility class to maximize code reusability & minimize code redundancy.
com.dev.invoice.rest.controllerInvoiceRestController.javaRest Controller the backbone of REST API, accepts all requests coming from client and handover to respective method for processing.
application.properties
#  DB Connection Properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/REST_INVOICE
spring.datasource.username=root
spring.datasource.password=devs

# JPA Properites
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
Invoice.java
package com.dev.invoice.rest.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Invoice {
	
	@Id
	@GeneratedValue
	private Long id;
	private String name;
	private Double amount;
	private Double finalAmount;
	private String number;
	private String receivedDate;
	private String type;
	private String vendor;
	private String comments;
}
InvoiceRepository.java
package com.dev.invoice.rest.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import com.dev.invoice.rest.entity.Invoice;

public interface InvoiceRepository extends JpaRepository<Invoice, Long>{
	
	// Update is Non-Select Operation, so @Modifying is used
	@Modifying
	@Query("UPDATE Invoice SET number=:number WHERE id=:id")
	Integer updateInvoiceNumberById(String number,Long id);
}
Service Interface IInvoiceService.java
package com.dev.invoice.rest.service;

import java.util.List;

import com.dev.invoice.rest.entity.Invoice;

public interface IInvoiceService {
	
	/**
	 * Takes Invoice Object as input and returns PK generated
	 */
	Long saveInvoice(Invoice inv);
	
	/**
	 * Takes existing Invoice data as input and updates values
	 */
	void updateInvoice(Invoice e);
	
	/**
	 * Takes PK(ID) as input and deletes Invoice Object data
	 */
	void deleteInvoice(Long id);	
	
	/**
	 * Takes id as input and returns one row as one object
	 */
	Invoice getOneInvoice(Long id);  //used in RestController
	
	/**
	 * select all rows and provides result as a List<Invoice>
	 */
	List<Invoice> getAllInvoices();
	
	/**
	 * Takes Id as input,checks if record exists returns true, else false
	 * 
	 */
	boolean isInvoiceExist(Long id);
	
	/**
	 * Takes 2 fields as input, updates Invoice data as provided where clause
	 * like 'UPDATE Invoice SET number=:number WHERE id=:id'
	 */
	Integer updateInvoiceNumberById(String number,Long id);
}
InvoiceServiceImpl.java
package com.dev.invoice.rest.service.impl;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dev.invoice.rest.entity.Invoice;
import com.dev.invoice.rest.exception.InvoiceNotFoundException;
import com.dev.invoice.rest.repo.InvoiceRepository;
import com.dev.invoice.rest.service.</yoastmark>IInvoiceService;
import com.dev.invoice.rest.util.InvoiceUtil;

@Service 
public class InvoiceServiceImpl implements IInvoiceService {
	
	@Autowired
	private InvoiceRepository repo;  
	
	@Autowired
	private InvoiceUtil util;
	
	@Override
	public Long saveInvoice(Invoice inv) {
		util.CalculateFinalAmountIncludingGST(inv);
		Long id = repo.save(inv).getId();
		return id;
	}

	@Override
	public void updateInvoice(Invoice inv) {
		util.CalculateFinalAmountIncludingGST(inv);
		repo.save(inv);
	}

	@Override
	public void deleteInvoice(Long id) {
		Invoice inv= getOneInvoice(id);
		repo.delete(inv);
	}
	
	public Optional<Invoice> getSingleInvoice(Long Id) {
		return repo.findById(Id);
	}

	@Override
	public Invoice getOneInvoice(Long id) {

		Invoice inv = repo.findById(id)
				.orElseThrow(()->new InvoiceNotFoundException(
						new StringBuffer().append("Product  '")
						.append(id)
						.append("' not exist")
						.toString())
						);
		return inv;
	}

	@Override
	public List<Invoice> getAllInvoices() {
		List<Invoice> list = repo.findAll();
		//JDK 1.8 List Sort (using Comparator)
				list.sort((ob1,ob2)->ob1.getId().intValue()-ob2.getId().intValue());
				//list.sort((ob1,ob2)->ob1.getAmount().compareTo(ob2.getAmount())); //ASC
				//list.sort((ob1,ob2)->ob2.getAmount().compareTo(ob1.getAmount())); // DESC
		return list;
	}
	
	@Override
	public boolean isInvoiceExist(Long id) {
		
		return repo.existsById(id);
	}
	
	@Override
	@Transactional
	public Integer updateInvoiceNumberById(
			String number, Long id) 
	{
		if(!repo.existsById(id)) { 
			throw new InvoiceNotFoundException(
					new StringBuffer()
					.append("Invoice '")
					.append(id)
					.append("' not exist")
					.toString());
		}
		return repo.updateInvoiceNumberById(number, id);
	}
	
}
ErrorType.java
package com.dev.invoice.rest.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorType {

	private String time;
	private String status;
	private String message;
	
}
InvoiceNotFoundException.java
package com.dev.invoice.rest.exception;

//Custom Exception
public class InvoiceNotFoundException extends RuntimeException{

	private static final long serialVersionUID = 1L;

	public InvoiceNotFoundException() {
		super();
	}
	
	public InvoiceNotFoundException(String message) {
		super(message);
	}
	
}
InvoiceErrorHandler.java
package com.dev.invoice.rest.exception.handler;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.dev.invoice.rest.entity.ErrorType;
import com.dev.invoice.rest.exception.InvoiceNotFoundException;

//@ControllerAdvice
@RestControllerAdvice
public class InvoiceErrorHandler {
	/**
	 * In case of InvoiceNotFoundException is thrown
	 * from any controller method, this logic gets
	 * executed which behaves like re-usable and
	 * clear code (Code Modularity)
	 * @param nfe
	 * @return ResponseEntity
	 */
	//@ResponseBody
	@ExceptionHandler(InvoiceNotFoundException.class)
	public ResponseEntity<ErrorType> handleNotFound(InvoiceNotFoundException nfe){
		
		return new ResponseEntity<ErrorType>(
				new ErrorType(
						new Date(System.currentTimeMillis()).toString(), 
						"404- NOT FOUND", 
						nfe.getMessage()), 
				HttpStatus.</yoastmark>NOT_FOUND);
	}
}
Utility class InvoiceUtil.java
package com.dev.invoice.rest.util;

import org.springframework.stereotype.Component;

import com.dev.invoice.rest.entity.Invoice;

@Component
public class InvoiceUtil {
	
	public Invoice  CalculateFinalAmountIncludingGST (Invoice inv) {
		var amount=inv.getAmount();
		var gst= 0.1;
		var finalAmount=amount+(amount*gst);
		inv.setFinalAmount(finalAmount);
		return inv;
	}
	
	public void copyNonNullValues(Invoice req, Invoice db) {
		
		if(req.getName() !=null) {
			db.setName(req.getName());
		}
		
		if(req.getAmount() !=null) {
			db.setAmount(req.getAmount());
		}
		
		if(req.getNumber() !=null) {
			db.setNumber(req.getNumber());
		}
		
		if(req.getReceivedDate() !=null) {
			db.setReceivedDate(req.getReceivedDate());
		}
		
		if(req.getType() !=null) {
			db.setType(req.getType());
		}
		
		if(req.getVendor() !=null) {
			db.setVendor(req.getVendor());
		}
		
		if(req.getComments() !=null) {
			db.setComments(req.getComments());
		}
	}
}
InvoiceRestController.java
package com.dev.invoice.rest.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.dev.invoice.rest.entity.Invoice;
import com.dev.invoice.rest.exception.InvoiceNotFoundException;
import com.dev.invoice.rest.service.IInvoiceService;
import com.dev.invoice.rest.util.InvoiceUtil;

@RestController
@RequestMapping("/invoice/rest")
// @CrossOrigin(origins = "http://localhost:4200") //Required in case of Angular Client
public class InvoiceRestController {
	
	@Autowired
	private IInvoiceService service;  
	
	@Autowired
	private InvoiceUtil util;
	
	/**
	 * Takes Invoice Object as input and returns save Status as ResponseEntity<String>
	 */
	@PostMapping("/saveInvoice")
	public ResponseEntity<String> saveInvoice(@RequestBody Invoice inv){
		ResponseEntity<String> resp = null;
		try{
			Long id = service.saveInvoice(inv);
			resp= new ResponseEntity<String>(
					"Invoice '"+id+"' created",HttpStatus.CREATED); //201-created
		} catch (Exception e) {
			e.printStackTrace();
			resp = new ResponseEntity<String>(
					"Unable to save Invoice", 
					HttpStatus.INTERNAL_SERVER_ERROR); //500-Internal Server Error
		}
		return resp;
	}
	
	/**
	 * To retrieve all Invoices, returns data retrieval Status as ResponseEntity<?>
	 */
	@GetMapping("/getAllInvoices")
	public ResponseEntity<?> getAllInvoices() {
		ResponseEntity<?> resp=null;
		try {
			List<Invoice> list= service.getAllInvoices();
			resp= new ResponseEntity<List<Invoice>>(list,HttpStatus.OK);
		} catch (Exception e) {
			e.printStackTrace();
			resp = new ResponseEntity<String>(
					"Unable to get Invoice", 
					HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return resp;
	}
	
	/**
	 * To retrieve one Invoice by providing id, returns Invoice object & Status as ResponseEntity<?>
	 */
	@GetMapping("/find/{id}")
	public ResponseEntity<?> getOneInvoice(@PathVariable Long id){
		ResponseEntity<?> resp= null;
		try {
			Invoice inv= service.getOneInvoice(id);
			resp= new ResponseEntity<Invoice>(inv,HttpStatus.OK);
		}catch (InvoiceNotFoundException nfe) {
			throw nfe; 
		}catch (Exception e) {
			e.printStackTrace();
			resp = new ResponseEntity<String>(
					"Unable to find Invoice", 
					HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return resp;
	}
	
	/**
	 * To delete one Invoice by providing id, returns Status as ResponseEntity<String>
	 */
	@DeleteMapping("/remove/{id}")
	public ResponseEntity<String> deleteInvoice(@PathVariable Long id){
		
		ResponseEntity<String> resp= null;
		try {
			service.deleteInvoice(id);
			resp= new ResponseEntity<String> (
					"Invoice '"+id+"' deleted",HttpStatus.OK);
			
		} catch (InvoiceNotFoundException nfe) {
			throw nfe;
		} catch (Exception e) {
			e.printStackTrace();
			resp= new ResponseEntity<String>(
					"Unable to delete Invoice", HttpStatus.INTERNAL_SERVER_ERROR);
		}
		
		return resp;
	}
	
	/**
	 * To modify one Invoice by providing id, updates Invoice object & returns Status as ResponseEntity<String>
	 */
	@PutMapping("/modify/{id}")
	public ResponseEntity<String> updateInvoice(@PathVariable Long id, @RequestBody Invoice invoice){
		
		ResponseEntity<String> resp = null;
		try {
			//db Object
			Invoice inv= service.getOneInvoice(id);
			//copy non-null values from request to Database object
			util.copyNonNullValues(invoice, inv);
			//finally update this object
			service.updateInvoice(inv);
			resp = new ResponseEntity<String>(
					//"Invoice '"+id+"' Updated",
					HttpStatus.RESET_CONTENT); //205- Reset-Content(PUT)
			
		} catch (InvoiceNotFoundException nfe) {
			throw nfe; // re-throw exception to handler
		} catch (Exception e) {
			e.printStackTrace();
			resp = new ResponseEntity<String>(
					"Unable to Update Invoice", 
					HttpStatus.INTERNAL_SERVER_ERROR); //500-ISE
		}
		return resp;
	}
	
	/**
	 * To update one Invoice just like where clause condition, updates Invoice object & returns Status as ResponseEntity<String>
	 */
	@PatchMapping("/modify/{id}/{number}")
	public ResponseEntity<String> updateInvoiceNumberById(
			@PathVariable Long id,
			@PathVariable String number
			) 
	{
		ResponseEntity<String> resp = null;
		try {
			service.updateInvoiceNumberById(number, id);
			resp = new ResponseEntity<String>(
					"Invoice '"+number+"' Updated",
					HttpStatus.PARTIAL_CONTENT); //206- Reset-Content(PUT)
			
		} catch(InvoiceNotFoundException pne) {
			throw pne; // re-throw exception to handler
		} catch (Exception e) {
			e.printStackTrace();
			resp = new ResponseEntity<String>(
					"Unable to Update Invoice", 
					HttpStatus.INTERNAL_SERVER_ERROR); //500-ISE
		}
		return resp;
	}
}

Finally your project structure would look like below screen.

How To Develop REST API Using Spring Boot ?

How to run the application ?

Further to run the application for testing purpose, right click on Project and then select Run As >> Spring Boot App. You can also package it into a jar & run it accordingly. Additionally, to test the app you need to have a Client application/software.

How to test the application ?

As discussed in introduction part, there are multiple ways to test the REST application. At this point, we will suggest you to use the most popular tool ‘POSTMAN’. You can also download it from here.

♠ Testing saveInvoice() method : [http://localhost:8080/invoice/rest/saveInvoice]

Open Postman software, Select method ‘POST’ from dropdown, enter below URL, select ‘Body’ then click on ‘raw’, select ‘JSON” from dropdown. All the above selections are highlighted in the below screenshot. Now Enter data in JSON format. If you want to know ‘how to write data in JSON format’ visit internal link. Once you enter the JSON data, click on ‘Send’ button & check the successful message in lower box.

http://localhost:8080/invoice/rest/saveInvoice

How to develop REST API using Spring Boot

♦ Testing getAllInvoices() method : [http://localhost:8080/invoice/rest/getAllInvoices]

Select method ‘GET’ from dropdown, then enter below URL. Now click on ‘Send’ button & check the list of all invoices as a JSON format in lower box.

http://localhost:8080/invoice/rest/getAllInvoices

♠ Testing getOneInvoice() method : [http://localhost:8080/invoice/rest/find/{id}]

{id} represents dynamic data. Suppose we want to retrieve Invoice whose id is 2. Select method ‘GET’ from dropdown, then enter below URL. Then click on ‘Send’ button & check the invoice with given id as a JSON format in lower box.

http://localhost:8080/invoice/rest/find/2

♦ Testing updateInvoice() method : [http://localhost:8080/invoice/rest/modify/{id}]

Select method ‘PUT’ from dropdown, enter below URL, select ‘Body’ then click on ‘raw’, select ‘JSON” from dropdown. Then Enter data in JSON format to be modified. Then click on ‘Send’ button and check the successful message in lower box.

http://localhost:8080/invoice/rest/modify/2

♠ Testing deleteInvoice() method : [http://localhost:8080/invoice/rest/remove/{id}]

Suppose you want to remove an Invoice where id is 2. Then use below pattern URL. Select method ‘DELETE’ from dropdown, then enter below URL. Then click on ‘Send’ button and check the successful message in lower box.

http://localhost:8080/invoice/rest/remove/2

♦ Testing updateInvoiceNumberById() method : [http://localhost:8080/invoice/rest/modify/{id}/{number}]

Suppose you want to modify name field of an Invoice where id is 1. Then use below pattern URL. Select method ‘PATCH’ from dropdown, enter below URL, select ‘Body’ then click on ‘raw’, select ‘JSON” from dropdown. Then Enter data in JSON format to be modified. Finally click on ‘Send’ button & check the successful message in lower box.

http://localhost:8080/invoice/rest/modify/1/Inv02345

Can we use this REST API in the real project ?

Of course. You have to change all occurrences of Entity Name as per your real project, then you may use it accordingly.

Summary

Almost every REST application will have these operations we learnt in this article. In other words, no REST API can be developed without these operations. So, you have learnt the mandatory concepts of ‘How to develop REST CRUD API using Spring Boot ?’. Furthermore, you can go through other article on How to consume REST API using RestTemplate using Spring Boot.

close

11 thoughts on “How to develop REST CRUD API using Spring Boot ?

  1. I want to say that this post is awesome, great written and include almost all necessary information.

    I would like to see more posts like this .

  2. This is a topic that’s near to my heart… Cheers! Exactly where are your contact details though?

Leave a Reply

Top