RestFul APIs
RestFul APIs are the interface that two computer systems use to communicate and exchange data over the internet using HTTPS ports. The APIs that follow the REST architecture design paradigm are called Rest APIs.
In this article, we will create a category API for our blog application. To learn more about the Category API and its fields and attributes, please refer to the Blog Application-Database Schema.
Steps to create Category API for Blog Application
Step 1- Create Category Class
In this step, we will create a model class for the Category entity. To learn more about the Category class attributes, Please, refer How to design Database Schema. For this, go to Model Package > Right-Click > New > Class > Create Category Class.
package com.paulsofts.blogapplicationservices.model;
import org.springframework.data.annotation.Id;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
@Table(name = "categories")
public class Category {
@Id
@Column(name = "cateogyId")
@GeneratedValue(strategy = GenerationType.AUTO)
private int categoryId;
//we can define database constraints such as maximum
//length of categoryTile is 100 and it must not be null
@Column(name = "categoryTitle", length=100, nullable=false)
private String categoryTitle;
@Column(name = "categoryDescription")
private String categoryDescription;
}
Step 2- Create CategoryDto Class
In this step, we will create the CategoryDto class that is used to transfer data from one layer to another and hides the implementation details of the database layer. For this, go to DTO Package > Right-Click > New > Class > Create CategoryDto Class. We are also using bean validation such as @NotEmpty, @Size, etc. To learn more about bean validation, please refer to Bean Validation – How to validate data using Bean Validation?.
package com.paulsofts.blogapplicationservices.dto;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class CategoryDto {
private int categoryId;
//we are using the annotations for bean validation
@NotEmpty
@Size(min = 4, message = "Category Title must conatins atleast 4 characters")
private String categoryTitle;
private String categoryDescription;
}
Step 3- Create CategoryRespository Interface
In this step, we will create the CategoryRepository interface that is used to perform persistence (database) layer operations. For this, go to Repository Package > Right-Click > New > Interface > Create the CategoryRepository Interface. We will need to extends CategoryRepository interface to JpaRepository<T, ID> and need to pass the model class and the wrapper class of the type of ID used in the model class.i.e., JpaRepository<Category, Integer>.
package com.paulsofts.blogapplicationservices.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.paulsofts.blogapplicationservices.model.Category;
/*
* In the arguments of JpaRepository, we need to pass the model class
* and the wrapper class of the type of id used in our model class
* */
public interface CategoryRepository extends JpaRepository<Category, Integer> {
}
Step 4- Create CategoryService Interface
In this step, we will create CategoryService interface. The service classes are used to perform all the business logic on the entity class data which are mapped to JPA. For this, go to Service Package > Right-Click > New > Interface > Create CategoryService Interface.
package com.paulsofts.blogapplicationservices.service;
import java.util.List;
import com.paulsofts.blogapplicationservices.dto.CategoryDto;
public interface CategoryService {
public CategoryDto createCategory(CategoryDto categoryDto);
public CategoryDto updateCategory(CategoryDto categoryDto, int categoryId);
public CategoryDto getCategoryById(int categoryId);
public List<CategoryDto> getAllCategory();
public void deleteCategory(int categoryId);
}
Step 5- Create CategoryServiceImpl Class
In this step, we will write the corresponding implementation class for the CategoriyService interface. For this, go to Service Package > Right-Click > New > Class > Create CategoryServiceImpl Class, and using this class, implement the CategoryService interface.
package com.paulsofts.blogapplicationservices.service;
import java.util.List;
import java.util.stream.Collectors;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.paulsofts.blogapplicationservices.dto.CategoryDto;
import com.paulsofts.blogapplicationservices.model.Category;
import com.paulsofts.blogapplicationservices.repository.CategoryRepository;
import com.paulsofts.blogapplicationservices.utils.ResourceNotFoundException;
@Service
public class CategoryServiceImpl implements CategoryService {
//auto-wiring CategroyRepository to perform persistence operations
@Autowired
private CategoryRepository categoryRepository;
//auto-wiring model mapper to map Category to CategoryDto and vice-versa
@Autowired
private ModelMapper modelMapper;
@Override
public CategoryDto createCategory(CategoryDto categoryDto) {
Category category = this.modelMapper.map(categoryDto, Category.class);
Category categorySavedToDB = this.categoryRepository.save(category);
//return type of our method is CategoryDto so, we need to
//map our Category object(.i.e., categorySavedToDB) to CategoryDto object
return this.modelMapper.map(categorySavedToDB, CategoryDto.class);
}
@Override
public CategoryDto updateCategory(CategoryDto categoryDto, int categoryId) {
//first we will check whether the Category is present
//in database or not
Category categoryToBeUpdated = this.categoryRepository.findById(categoryId).orElseThrow(
() -> new ResourceNotFoundException("Category", "categoryId", categoryId));
//if the category is present in the database we will update it
categoryToBeUpdated.setCategoryTitle(categoryDto.getCategoryTitle());
categoryToBeUpdated.setCategoryDescription(categoryDto.getCategoryDescription());
return this.modelMapper.map(categoryToBeUpdated, CategoryDto.class);
}
@Override
public CategoryDto getCategoryById(int categoryId) {
Category category = this.categoryRepository.findById(categoryId).orElseThrow(
() -> new ResourceNotFoundException("Category", "categoryId", categoryId));
return this.modelMapper.map(category, CategoryDto.class);
}
@Override
public List<CategoryDto> getAllCategory() {
List<Category> categoryList = this.categoryRepository.findAll();
//we are using streams api to change each category in categoryList
//to categoryDto and collecting it in categoryDtoList
List<CategoryDto> categoryDtoList = categoryList.stream().map(category ->
this.modelMapper.map(category, CategoryDto.class)).collect(Collectors.toList());
return categoryDtoList;
}
@Override
public void deleteCategory(int categoryId) {
Category category = this.categoryRepository.findById(categoryId).orElseThrow(() ->
new ResourceNotFoundException("Category", "categoryId", categoryId));
this.categoryRepository.delete(category);
}
}
Step 6- Create CategoryController Class
In this step, we will create the CategoryController class that is used to handle the incoming requests from the client side. For this, go to Controller Package > Right-Click > New > Class > Create CategoryController Class. We will also use the generic requests and responses. We have used the following GenericRequest<T> and GenericResponse<T> classes.
GenericRequest<CategoryDto> Class
package com.paulsofts.blogapplicationservices.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class GenericRequest<T> {
private T t;
}
GenericResponse<CategoryDto> Class
package com.paulsofts.blogapplicationservices.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class GenericResponse<T> {
//T is the generic type which we will take as input
private T t;
private String message;
private String response;
}
Now, we will complete the implementation of our CategoryController class.
package com.paulsofts.blogapplicationservices.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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.paulsofts.blogapplicationservices.dto.CategoryDto;
import com.paulsofts.blogapplicationservices.service.CategoryServiceImpl;
import com.paulsofts.blogapplicationservices.utils.GenericRequest;
import com.paulsofts.blogapplicationservices.utils.GenericResponse;
@RestController
@RequestMapping("/api/categories")
public class CategoryController {
@Autowired
private CategoryServiceImpl categoryServiceImpl;
@PostMapping("/create")
public ResponseEntity<GenericResponse<CategoryDto>> createCategory(@RequestBody GenericRequest<CategoryDto> request){
//as we have used the GenericRequest, we need to pull out our model
//class data using the getT() method of generic class which will be used as request body
CategoryDto createdCategoyDto = this.categoryServiceImpl.createCategory(request.getT());
return new ResponseEntity<GenericResponse<CategoryDto>>(
new GenericResponse<CategoryDto>(createdCategoyDto, "category created", "OK"), HttpStatus.CREATED);
}
@PostMapping("/update/{categoryId}")
public ResponseEntity<GenericResponse<CategoryDto>> updateCategory(
@RequestBody GenericRequest<CategoryDto> request, @PathVariable("categoryId") int categoryId){
CategoryDto updatedCategoryDto = this.categoryServiceImpl.updateCategory(request.getT(), categoryId);
return new ResponseEntity<GenericResponse<CategoryDto>>(
new GenericResponse<CategoryDto>(updatedCategoryDto, "category updated", "OK"), HttpStatus.OK);
}
@GetMapping("/get/{categoryId}")
public ResponseEntity<GenericResponse<CategoryDto>> getCategoryById(@PathVariable("categoryId") int categoryId){
CategoryDto categoryDto = this.categoryServiceImpl.getCategoryById(categoryId);
return new ResponseEntity<GenericResponse<CategoryDto>>(
new GenericResponse<CategoryDto>(categoryDto, "category", "OK"), HttpStatus.OK);
}
@GetMapping("/get")
public ResponseEntity<GenericResponse<List<CategoryDto>>> getAllCategory(){
List<CategoryDto> categoryDtoList = this.categoryServiceImpl.getAllCategory();
return new ResponseEntity<GenericResponse<List<CategoryDto>>>(
new GenericResponse<List<CategoryDto>>(categoryDtoList, "category list", "OK"), HttpStatus.OK);
}
//as return type of deleteUser() method of service class is void
//we will be using String as type of our GenericResponse class
@DeleteMapping("/delete/{categoryId}")
public ResponseEntity<GenericResponse<String>> deleteCategory(@PathVariable("categoryId") int categoryId){
this.categoryServiceImpl.deleteCategory(categoryId);
return new ResponseEntity<GenericResponse<String>>(
new GenericResponse<String>("categoryId " + categoryId, "category deleted", "OK"), HttpStatus.OK);
}
}
Step 7- Add Bean Validation
In this step, we will need to add bean validation for our Category APIs. For this, we need to update our CategoryController class. We will add @Valid annotation to the methods where we are taking the CategoryDto Object (Model Class Object). We need to add @Valid annotation to GenericRequest<T> class also as we are pulling the request body (CategoryDto object) from generic type T. To learn more about bean validation please refer, Bean Validation – How to validate data using Bean Validation?.
package com.paulsofts.blogapplicationservices.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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.paulsofts.blogapplicationservices.dto.CategoryDto;
import com.paulsofts.blogapplicationservices.service.CategoryServiceImpl;
import com.paulsofts.blogapplicationservices.utils.GenericRequest;
import com.paulsofts.blogapplicationservices.utils.GenericResponse;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/categories")
public class CategoryController {
@Autowired
private CategoryServiceImpl categoryServiceImpl;
//we have added the @Valid annotation for bean validation
@PostMapping("/create")
public ResponseEntity<GenericResponse<CategoryDto>> createCategory(@Valid @RequestBody GenericRequest<CategoryDto> request){
//as we have used the GenericRequest, we need to pull out our model
//class data using the getT() method of generic class which will be used as request body
CategoryDto createdCategoyDto = this.categoryServiceImpl.createCategory(request.getT());
return new ResponseEntity<GenericResponse<CategoryDto>>(
new GenericResponse<CategoryDto>(createdCategoyDto, "category created", "OK"), HttpStatus.CREATED);
}
@PostMapping("/update/{categoryId}")
public ResponseEntity<GenericResponse<CategoryDto>> updateCategory(@Valid
@RequestBody GenericRequest<CategoryDto> request, @PathVariable("categoryId") int categoryId){
CategoryDto updatedCategoryDto = this.categoryServiceImpl.updateCategory(request.getT(), categoryId);
return new ResponseEntity<GenericResponse<CategoryDto>>(
new GenericResponse<CategoryDto>(updatedCategoryDto, "category updated", "OK"), HttpStatus.OK);
}
@GetMapping("/get/{categoryId}")
public ResponseEntity<GenericResponse<CategoryDto>> getCategoryById(@PathVariable("categoryId") int categoryId){
CategoryDto categoryDto = this.categoryServiceImpl.getCategoryById(categoryId);
return new ResponseEntity<GenericResponse<CategoryDto>>(
new GenericResponse<CategoryDto>(categoryDto, "category", "OK"), HttpStatus.OK);
}
@GetMapping("/get")
public ResponseEntity<GenericResponse<List<CategoryDto>>> getAllCategory(){
List<CategoryDto> categoryDtoList = this.categoryServiceImpl.getAllCategory();
return new ResponseEntity<GenericResponse<List<CategoryDto>>>(
new GenericResponse<List<CategoryDto>>(categoryDtoList, "category list", "OK"), HttpStatus.OK);
}
//as return type of deleteUser() method of service class is void
//we will be using String as type of our GenericResponse class
@DeleteMapping("/delete/{categoryId}")
public ResponseEntity<GenericResponse<String>> deleteCategory(@PathVariable("categoryId") int categoryId){
this.categoryServiceImpl.deleteCategory(categoryId);
return new ResponseEntity<GenericResponse<String>>(
new GenericResponse<String>("categoryId " + categoryId, "category deleted", "OK"), HttpStatus.OK);
}
}
Step 8- Test Application
In this step, we will test our complete Category APIs. Once we run our application, we can check the logs for the categories table created in our database.
We can also check our database schema for the categories table.
Now, first we will try to validate our category APIs, and for that we will send a bad request.
As in above picture, we can see our bean validation is working fine. Now, we will try to perform CRUD Operations using Category APIs.
Category- Create Request
Category- Update Request
Category- Get Request
Category- GetAll Request
Category- Delete Request