让我们使用 Java 和 Spring 从头开发一个电子商务应用程序
项目设置,开发类别和产品 API
动机
在我看来,学习编程的最佳方式是创建一个具有实际用途的真实项目,这样整个学习过程就会变得非常精彩。此外,你可以在作品集中展示你的应用,这对你获得自由职业或面试机会大有裨益。
在本系列博客中,你将学习如何从零开始构建电商平台,从而提升你的开发技能。首先,你需要熟悉Java和 Spring Boot(我们将用它们来构建后端)以及Vue.js(我们将用它构建前端)。
读者须知:
虽然我已经构建了整个应用程序并编写了一系列教程,这些教程非常受欢迎并且在谷歌搜索结果中名列前茅,对此我感到非常自豪,(仅在 Medium 上的浏览量就超过 13 万次)
后来我发现这些教程有些内容缺失,有些已经不再适用了。比如,有些教程里我们用原生 JS 开发了一个Android 应用,但后来就放弃了。
因此,我尝试重新制作教程,删除/编辑一些不再相关的部分,并创建一些涵盖缺失部分的教程,以便用户可以非常轻松地遵循教程。
视频教程
播放列表
Vue 前端教程

让我们使用 Spring Boot 和 Vue.js 从头开始开发一个电子商务应用程序
Nil Madhab ・ 2021年10月30日
创建项目
-
首先,转到https://start.spring.io/,我们可以在其中创建新的 spring 应用程序并添加依赖项
-
选择maven,添加Spring Data JPA和Spring Web依赖
- 单击“生成”并下载 .zip 文件,解压缩并使用IntelliJ Idea打开它
项目结构
主类
项目的 src/main/java 文件夹包含一个具有 main 方法的类。它是应用程序的入口点。
应用程序.属性
在src/main/resources文件夹中,会有一个名为application.properties的文件。该文件主要负责向 Spring 传达我们所做的配置以及如何创建对象。换句话说,它在控制反转 (IoC)中扮演着重要的角色。
pom.xml
在项目文件夹中,会有一个名为的文件pom.xml
。我们将在此文件中添加所有必需的依赖项。
现在,项目结构如下-
您可以在下面给出的 GitHub 存储库分支中检查后端的项目结构 -
GitHub — webtutsplus/ecommerce
我们的后端应用程序概述
在这个 Spring 应用程序中,以下是您在开始之前必须了解的重要包。
这就是 Spring 架构。外界调用REST API ,REST API 与控制器交互,控制器与服务交互,服务调用存储库。
存储库与数据库交互。我们遵循这种模式,使代码库易于维护,避免出现意大利面条式的代码,长远来看,这可能是一场噩梦。
模型/实体
模型是与数据库中表的结构直接相关的基本实体。换句话说,这些模型充当容器,保存相似和相关的数据,用于将这些数据从客户端传输到数据库。用户资料、产品和类别是我们后端应用程序中的一些模型。
存储库
存储库 (Repository) 是充当数据库和应用程序之间桥梁的接口。它将模型数据传入和传出数据库。每个模型都有一个唯一的存储库用于数据传输。
服务
服务是架构的一部分,在这里实例化存储库并应用业务逻辑。客户端到达这里的数据经过处理后,通过存储库发送到数据库。
控制器
控制器是架构中首先处理来自客户端请求的部分。它控制着应该在后端运行的进程以及需要发送给客户端的响应。它与服务交互,服务又与存储库交互,存储库又使用模型与数据库交互。
数据之旅
设计类别 API
一旦我们准备好基本结构,就可以为我们的电子商务商店添加一些产品和类别了。
举个例子,我们可以创建一个“鞋子”类别,其中包含不同类型的鞋子作为产品。因此,一个类别可以包含多个产品,但每个产品都属于一个类别。
模型
首先我们将创建一个模型,Category它将有四个字段。
-
ID
-
类别名称
-
描述
-
图片网址
我们还将为这四个字段创建一个 setter 和 getter。
它在数据库中会有对应的表类别
package com.educative.ecommerce.model; | |
import javax.persistence.CascadeType; | |
import javax.persistence.Column; | |
import javax.persistence.Entity; | |
import javax.persistence.FetchType; | |
import javax.persistence.GeneratedValue; | |
import javax.persistence.GenerationType; | |
import javax.persistence.Id; | |
import javax.persistence.OneToMany; | |
import javax.persistence.Table; | |
import javax.validation.constraints.NotBlank; | |
import java.util.Set; | |
@Entity | |
@Table(name = "categories") | |
public class Category { | |
@Id | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | |
private Integer id; | |
@Column(name = "category_name") | |
private @NotBlank String categoryName; | |
private @NotBlank String description; | |
private @NotBlank String imageUrl; | |
public Category() { | |
} | |
public Category(@NotBlank String categoryName, @NotBlank String description) { | |
this.categoryName = categoryName; | |
this.description = description; | |
} | |
public Category(@NotBlank String categoryName, @NotBlank String description, @NotBlank String imageUrl) { | |
this.categoryName = categoryName; | |
this.description = description; | |
this.imageUrl = imageUrl; | |
} | |
public String getCategoryName() { | |
return this.categoryName; | |
} | |
public void setCategoryName(String categoryName) { | |
this.categoryName = categoryName; | |
} | |
public String getDescription() { | |
return description; | |
} | |
public void setDescription(String description) { | |
this.description = description; | |
} | |
@Override | |
public String toString() { | |
return "User {category id=" + id + ", category name='" + categoryName + "', description='" + description + "'}"; | |
} | |
public String getImageUrl() { | |
return imageUrl; | |
} | |
public void setImageUrl(String imageUrl) { | |
this.imageUrl = imageUrl; | |
} | |
public Integer getId() { | |
return id; | |
} | |
public void setId(Integer id) { | |
this.id = id; | |
} | |
} |
package com.educative.ecommerce.model; | |
import javax.persistence.CascadeType; | |
import javax.persistence.Column; | |
import javax.persistence.Entity; | |
import javax.persistence.FetchType; | |
import javax.persistence.GeneratedValue; | |
import javax.persistence.GenerationType; | |
import javax.persistence.Id; | |
import javax.persistence.OneToMany; | |
import javax.persistence.Table; | |
import javax.validation.constraints.NotBlank; | |
import java.util.Set; | |
@Entity | |
@Table(name = "categories") | |
public class Category { | |
@Id | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | |
private Integer id; | |
@Column(name = "category_name") | |
private @NotBlank String categoryName; | |
private @NotBlank String description; | |
private @NotBlank String imageUrl; | |
public Category() { | |
} | |
public Category(@NotBlank String categoryName, @NotBlank String description) { | |
this.categoryName = categoryName; | |
this.description = description; | |
} | |
public Category(@NotBlank String categoryName, @NotBlank String description, @NotBlank String imageUrl) { | |
this.categoryName = categoryName; | |
this.description = description; | |
this.imageUrl = imageUrl; | |
} | |
public String getCategoryName() { | |
return this.categoryName; | |
} | |
public void setCategoryName(String categoryName) { | |
this.categoryName = categoryName; | |
} | |
public String getDescription() { | |
return description; | |
} | |
public void setDescription(String description) { | |
this.description = description; | |
} | |
@Override | |
public String toString() { | |
return "User {category id=" + id + ", category name='" + categoryName + "', description='" + description + "'}"; | |
} | |
public String getImageUrl() { | |
return imageUrl; | |
} | |
public void setImageUrl(String imageUrl) { | |
this.imageUrl = imageUrl; | |
} | |
public Integer getId() { | |
return id; | |
} | |
public void setId(Integer id) { | |
this.id = id; | |
} | |
} |
我们对该类别使用了 @NotBlank 注释。为此,我们必须在 pom.xml 文件中包含以下依赖项。
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
存储库
现在我们将创建一个扩展 JpaRepository 的存储库 Categoryrepository.java。
它将有一个方法 findByCategoryName。
package com.educative.ecommerce.repository; | |
import com.educative.ecommerce.model.Category; | |
import org.springframework.data.jpa.repository.JpaRepository; | |
import org.springframework.stereotype.Repository; | |
@Repository | |
public interface Categoryrepository extends JpaRepository<Category, Integer> { | |
Category findByCategoryName(String categoryName); | |
} |
package com.educative.ecommerce.repository; | |
import com.educative.ecommerce.model.Category; | |
import org.springframework.data.jpa.repository.JpaRepository; | |
import org.springframework.stereotype.Repository; | |
@Repository | |
public interface Categoryrepository extends JpaRepository<Category, Integer> { | |
Category findByCategoryName(String categoryName); | |
} |
服务
现在我们将创建一个 CategoryService 文件,负责创建、更新或获取存储库。
Categoryrepository 具有内置方法 findAll()、save(),因为它扩展了 JpaRepository
package com.educative.ecommerce.service; | |
import com.educative.ecommerce.model.Category; | |
import com.educative.ecommerce.repository.Categoryrepository; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import javax.transaction.Transactional; | |
import java.util.List; | |
import java.util.Optional; | |
@Service | |
public class CategoryService { | |
@Autowired | |
private Categoryrepository categoryrepository; | |
public List<Category> listCategories() { | |
return categoryrepository.findAll(); | |
} | |
public void createCategory(Category category) { | |
categoryrepository.save(category); | |
} | |
public Category readCategory(String categoryName) { | |
return categoryrepository.findByCategoryName(categoryName); | |
} | |
public Optional<Category> readCategory(Integer categoryId) { | |
return categoryrepository.findById(categoryId); | |
} | |
public void updateCategory(Integer categoryID, Category newCategory) { | |
Category category = categoryrepository.findById(categoryID).get(); | |
category.setCategoryName(newCategory.getCategoryName()); | |
category.setDescription(newCategory.getDescription()); | |
category.setImageUrl(newCategory.getImageUrl()); | |
categoryrepository.save(category); | |
} | |
} |
package com.educative.ecommerce.service; | |
import com.educative.ecommerce.model.Category; | |
import com.educative.ecommerce.repository.Categoryrepository; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import javax.transaction.Transactional; | |
import java.util.List; | |
import java.util.Optional; | |
@Service | |
public class CategoryService { | |
@Autowired | |
private Categoryrepository categoryrepository; | |
public List<Category> listCategories() { | |
return categoryrepository.findAll(); | |
} | |
public void createCategory(Category category) { | |
categoryrepository.save(category); | |
} | |
public Category readCategory(String categoryName) { | |
return categoryrepository.findByCategoryName(categoryName); | |
} | |
public Optional<Category> readCategory(Integer categoryId) { | |
return categoryrepository.findById(categoryId); | |
} | |
public void updateCategory(Integer categoryID, Category newCategory) { | |
Category category = categoryrepository.findById(categoryID).get(); | |
category.setCategoryName(newCategory.getCategoryName()); | |
category.setDescription(newCategory.getDescription()); | |
category.setImageUrl(newCategory.getImageUrl()); | |
categoryrepository.save(category); | |
} | |
} |
控制器
我们将创建一个辅助类 ApiResponse.java,用于返回 API 的响应。
package com.educative.ecommerce.common; | |
import java.time.LocalDateTime; | |
public class ApiResponse { | |
private final boolean success; | |
private final String message; | |
public ApiResponse(boolean success, String message) { | |
this.success = success; | |
this.message = message; | |
} | |
public boolean isSuccess() { | |
return success; | |
} | |
public String getMessage() { | |
return message; | |
} | |
public String getTimestamp() { | |
return LocalDateTime.now().toString(); | |
} | |
} |
package com.educative.ecommerce.common; | |
import java.time.LocalDateTime; | |
public class ApiResponse { | |
private final boolean success; | |
private final String message; | |
public ApiResponse(boolean success, String message) { | |
this.success = success; | |
this.message = message; | |
} | |
public boolean isSuccess() { | |
return success; | |
} | |
public String getMessage() { | |
return message; | |
} | |
public String getTimestamp() { | |
return LocalDateTime.now().toString(); | |
} | |
} |
现在我们将创建包含所有 API 的控制器
我们将为类别创建 3 个 API
-
创造
-
更新
-
列出所有类别
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characterspackage com.educative.ecommerce.controllers; import com.educative.ecommerce.common.ApiResponse; import com.educative.ecommerce.model.Category; import com.educative.ecommerce.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; 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 javax.validation.Valid; import java.util.List; import java.util.Objects; @RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; @GetMapping("/") public ResponseEntity<List<Category>> getCategories() { List<Category> body = categoryService.listCategories(); return new ResponseEntity<>(body, HttpStatus.OK); } @PostMapping("/create") public ResponseEntity<ApiResponse> createCategory(@Valid @RequestBody Category category) { if (Objects.nonNull(categoryService.readCategory(category.getCategoryName()))) { return new ResponseEntity<ApiResponse>(new ApiResponse(false, "category already exists"), HttpStatus.CONFLICT); } categoryService.createCategory(category); return new ResponseEntity<>(new ApiResponse(true, "created the category"), HttpStatus.CREATED); } @PostMapping("/update/{categoryID}") public ResponseEntity<ApiResponse> updateCategory(@PathVariable("categoryID") Integer categoryID, @Valid @RequestBody Category category) { // Check to see if the category exists. if (Objects.nonNull(categoryService.readCategory(categoryID))) { // If the category exists then update it. categoryService.updateCategory(categoryID, category); return new ResponseEntity<ApiResponse>(new ApiResponse(true, "updated the category"), HttpStatus.OK); } // If the category doesn't exist then return a response of unsuccessful. return new ResponseEntity<>(new ApiResponse(false, "category does not exist"), HttpStatus.NOT_FOUND); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characterspackage com.educative.ecommerce.controllers; import com.educative.ecommerce.common.ApiResponse; import com.educative.ecommerce.model.Category; import com.educative.ecommerce.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; 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 javax.validation.Valid; import java.util.List; import java.util.Objects; @RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; @GetMapping("/") public ResponseEntity<List<Category>> getCategories() { List<Category> body = categoryService.listCategories(); return new ResponseEntity<>(body, HttpStatus.OK); } @PostMapping("/create") public ResponseEntity<ApiResponse> createCategory(@Valid @RequestBody Category category) { if (Objects.nonNull(categoryService.readCategory(category.getCategoryName()))) { return new ResponseEntity<ApiResponse>(new ApiResponse(false, "category already exists"), HttpStatus.CONFLICT); } categoryService.createCategory(category); return new ResponseEntity<>(new ApiResponse(true, "created the category"), HttpStatus.CREATED); } @PostMapping("/update/{categoryID}") public ResponseEntity<ApiResponse> updateCategory(@PathVariable("categoryID") Integer categoryID, @Valid @RequestBody Category category) { // Check to see if the category exists. if (Objects.nonNull(categoryService.readCategory(categoryID))) { // If the category exists then update it. categoryService.updateCategory(categoryID, category); return new ResponseEntity<ApiResponse>(new ApiResponse(true, "updated the category"), HttpStatus.OK); } // If the category doesn't exist then return a response of unsuccessful. return new ResponseEntity<>(new ApiResponse(false, "category does not exist"), HttpStatus.NOT_FOUND); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characterspackage com.educative.ecommerce.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket productApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(getApiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.educative.ecommerce")) .paths(PathSelectors.any()) .build(); } private ApiInfo getApiInfo() { Contact contact = new Contact("webtutsplus", "http://webtutsplus.com", "contact.webtutsplus@gmail.com"); return new ApiInfoBuilder() .title("Ecommerce API") .description("Documentation Ecommerce api") .version("1.0.0") .license("Apache 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") .contact(contact) .build(); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characterspackage com.educative.ecommerce.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket productApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(getApiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.educative.ecommerce")) .paths(PathSelectors.any()) .build(); } private ApiInfo getApiInfo() { Contact contact = new Contact("webtutsplus", "http://webtutsplus.com", "contact.webtutsplus@gmail.com"); return new ApiInfoBuilder() .title("Ecommerce API") .description("Documentation Ecommerce api") .version("1.0.0") .license("Apache 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") .contact(contact) .build(); } } <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-bean-validators</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
We also have to modify our application.properties file by adding the lines
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
Now, run the code and open [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html) page. We will see this screen

## Demo
Let’s create a category watch, with this request body. (Note: we do not need to pass id here, it will be auto created.)
{
"categoryName": "watches",
"description": "best watches",
"imageUrl": "https://images.unsplash.com/photo-1524805444758-089113d48a6d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80"
}

You will get the response as below-

Now, let us hit the get API

We will get the following response-
[
{
"id": 1,
"categoryName": "watches",
"description": "best watches",
"imageUrl": "https://images.unsplash.com/photo-1524805444758-089113d48a6d?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=634&q=80"
}
]
## Enable CORS
We will add the webconfig.java file, so that our [front end](https://medium.com/javarevisited/6-best-frontend-development-courses-for-beginners-to-learn-in-2021-f2772157864) can hit the API.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@Configuration
class Webconfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS");
}
};
}
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
@Configuration
class Webconfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "PUT", "POST", "PATCH", "DELETE", "OPTIONS");
}
};
}
}
Hurray! We can now play with the APIs and can create some new category, update and fetch all the categories.
Designing the Product API
Now we have some categories, it is time to make the products APIs. First, we will create the model, then we will create the repository, then we will make the service and at the end, we will create the controller and test it.
Model
Product will have id, name, imageURL, price, description as well as a foreign key to category, as every product belong to a category.
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private @NotNull String name;
private @NotNull String imageURL;
private @NotNull double price;
private @NotNull String description;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "category_id", nullable = false)
Category category;
public Product(String name, String imageURL, double price, String description, Category category) {
super();
this.name = name;
this.imageURL = imageURL;
this.price = price;
this.description = description;
this.category = category;
}
// setters and getters
}
Repository
Next, we will create a file, ProductRepository.java in repository package, which will just extend JpaRepository. If we need some methods, we will add it later
package com.educative.ecommerce.repository;
import com.educative.ecommerce.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
Service
Now we are ready to create the service class. Create a file ProductService.java in service directory. It will have an autowired ProductRepository.
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
}
DTO concept#
Before creating a product, we need to understand, what is a DTO (data transfer object)
Martin Fowler introduced the concept of a Data Transfer Object (DTO) as an object that carries data between processes.
In category controller, we directly used the model as request body, but that is not practical in many cases. We need to create a different object because
sometimes we might have to change the model, and we do not want to change the API for backward compatibility
We can’t use the model as request body if it has relationship with another model.
So quickly, we will create a package dto and inside the package we will create another package product, and there we will create our ProductDto.java class, which will have the following attributes
private Integer id;
private @NotNull String name;
private @NotNull String imageURL;
private @NotNull double price;
private @NotNull String description;
private @NotNull Integer categoryId;
We are also passing categoryId, because we need this to link a product with a category.
Controller
Now as we have the productDto ready, now time to create ProductController.java class
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
ProductService productService;
@Autowired
CategoryService categoryService;
}
It will autowire ProductService and CategoryService
Create a new product API
@PostMapping("/add")
public ResponseEntity<ApiResponse> addProduct(@RequestBody ProductDto productDto) {
Optional<Category> optionalCategory = categoryService.readCategory(productDto.getCategoryId());
if (!optionalCategory.isPresent()) {
return new ResponseEntity<ApiResponse>(new ApiResponse(false, "category is invalid"), HttpStatus.CONFLICT);
}
Category category = optionalCategory.get();
productService.addProduct(productDto, category);
return new ResponseEntity<>(new ApiResponse(true, "Product has been added"), HttpStatus.CREATED);
}
We will receive categoryId and product details from the request body.
First, we will check if the categoryId is valid or return “category is invalid” error.
Then we will create a product by calling method, productService.addProduct which takes productDto and category as arguments.
public void addProduct(ProductDto productDto, Category category) {
Product product = getProductFromDto(productDto, category);
productRepository.save(product);
}
public static Product getProductFromDto(ProductDto productDto, Category category) {
Product product = new Product();
product.setCategory(category);
product.setDescription(productDto.getDescription());
product.setImageURL(productDto.getImageURL());
product.setPrice(productDto.getPrice());
product.setName(productDto.getName());
return product;
}
The complete code can be found in the GitHub repository given below-
GitHub — webtutsplus/ecommerce at product-apis
And this marks the end of this tutorial. But wait! The tutorial series will continue for building the UI using Vue.js for the above-developed backend application. Till that, stay tuned!
Happy Learning
Continue to the next tutorial, where we will use the API to make a frontend using vue.js