0

[Series Thực Chiến E-commerce] Bài 14: Đưa hàng lên kệ - Thiết kế Product Model & API Thêm Sản Phẩm

Chào anh em!

Vậy là chúng ta đã chính thức "phá đảo" xong module User với đầy đủ các tính năng ăn chơi từ đăng ký, đăng nhập đến phân quyền các kiểu. Hôm nay, series của chúng ta sẽ bước sang một trang hoàn toàn mới, chạm vào trái tim của mọi hệ thống thương mại điện tử: Module Sản Phẩm (Product).

Làm e-commerce mà không có hàng hóa thì lấy gì mà bán, đúng không? Dữ liệu của sản phẩm thường phức tạp hơn User rất nhiều vì nó dính dáng đến danh mục (Category), giá cả, kho hàng, biến thể (màu sắc) và cả đánh giá (Rating).

Bắt tay vào "nhập kho" lô hàng đầu tiên nhé anh em!

1. Đúc khuôn cho Sản Phẩm (Model)

Anh em tạo file models/product.js. Cấu trúc của Product sẽ khá đồ sộ, hãy nhìn kỹ các trường (fields) nhé:

const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
  title: { type: String, required: true, trim: true },
  
  // 💡 TRỌNG ĐIỂM SEO: Trường Slug (vd: dien-thoai-iphone-15)
  slug: { type: String, required: true, unique: true, lowercase: true },
  
  description: { type: String, required: true },
  brand: { type: String, required: true },
  price: { type: Number, required: true },
  
  // Tham chiếu (ref) sang collection Category
  category: { type: mongoose.Types.ObjectId, ref: 'Category' },
  
  quantity: { type: Number, default: 0 },
  sold: { type: Number, default: 0 },
  images: { type: Array },
  
  // ❗ GÓP Ý NHỎ: Chỗ enum này hình như anh em gõ nhầm 'Brown' thành 'Grown' (Lớn lên) rồi này 😂
  color: { type: String, enum: ['Black', 'Brown', 'Red'] }, 
  
  // Mảng chứa các lượt đánh giá, tham chiếu thẳng đến người đánh giá (User)
  ratings: [
    {
      star: { type: Number },
      postedBy: { type: mongoose.Types.ObjectId, ref: 'User' },
      comment: { type: String },
    },
  ],
  totalRatings: { type: Number, default: 0 },
}, {
  timestamps: true,
});

module.exports = mongoose.model('Products', productSchema);

Kinh nghiệm thực chiến: Anh em để ý trường slug không? Khi người dùng lướt web, thay vì nhìn thấy URL gớm ghiếc kiểu domain.com/product/64abc123..., họ sẽ thấy domain.com/product/dien-thoai-iphone-15. Cái này gọi là Friendly URL, cực kỳ quan trọng để SEO lên top Google. Tí nữa mình sẽ dùng thư viện để tự động generate cái chuỗi này từ title.

2. Controller xử lý Nhập kho (Add Product)

Để làm được trò "biến title thành slug" tự động, anh em nhớ mở terminal lên cài thêm thư viện này nhé: npm i slugify.

Xong xuôi thì tạo file controllers/product.js:

const Product = require('../models/product');
const asyncHandler = require('express-async-handler');
const slugify = require('slugify'); // Import tool tạo slug

const createProduct = asyncHandler(async (req, res) => {
  // Validate cơ bản: Body rỗng thì đuổi về
  if (!req.body || Object.keys(req.body).length === 0) {
    throw new Error('Missing inputs - Bạn chưa nhập thông tin sản phẩm');
  }

  // Bắt buộc phải có tên sản phẩm để còn tạo Slug
  if (!req.body.title || req.body.title.trim() === '') {
    throw new Error('Product title is required - Tên sản phẩm không được bỏ trống');
  }

  // 💡 AUTO GEN SLUG: Khách chỉ cần nhập Title, Server tự lo phần Slug
  req.body.slug = slugify(req.body.title);

  // Tiến hành lưu vào Database
  const newProduct = await Product.create(req.body);

  return res.status(200).json({
    success: true,
    product: newProduct,
  });
});

module.exports = {
  createProduct,
    };

3. Nối dây ra ngoài (Router)

Anh em tạo thêm một filerouters/product.js (nhớ viết route cho nó, và vì đây là thao tác thêm data nên phải bế hai ông thần verifyAccessTokenisAdmin ra gác cửa nhé - chỉ Admin mới được thêm sản phẩm!).

Sau đó, vào trạm điều phối trung tâm routers/index.js khai báo module mới này:

const userRouter = require('./user');
const productRouter = require('./product'); // Import Product Router
const { notFound, errHandler } = require('../middlewares/errHandler');

const initRouter = (app) => {
  app.use('/api/user', userRouter);
  
  // Mở thêm một tuyến đường mới cho Product
  app.use('/api/product', productRouter);

  // Middlewares hứng lỗi vẫn phải nằm chốt chặn cuối cùng
  app.use(notFound);
  app.use(errHandler);
};

module.exports = initRouter;

Lời kết

Bật Postman lên, lấy quyền Admin và bắn thử một request POST nhét đầy đủ title, price, description... vào xem sao. Nếu trả về data thành công và anh em thấy trường slug được tự động format rất đẹp (có gạch nối giữa các chữ) thì xin chúc mừng!

Hàng đã vào kho rồi, bây giờ khách hàng ở ngoài Frontend muốn click vào xem chi tiết một sản phẩm thì sao? Đó sẽ là nhiệm vụ của APILấy thông tin sản phẩm theo ID.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí