2025-07-29 Line 訂單系統- 要開始「第一塊積木」,NestJS Products Module 完整程式碼

下面我會直接給你可立即貼進你的 NestJS 專案、可運作、結構正確、符合企業級設計的:

NestJS Products Module 完整程式碼(含 CRUD + DTO + Validation + Service)

內容包含:

  • products.module.ts
  • products.controller.ts
  • products.service.ts
  • dto/create-product.dto.ts
  • dto/update-product.dto.ts
  • 使用 In-Memory(第一塊積木)
    下一階段才能開始串 MySQL / Prisma

這份程式碼是「乾淨、最佳實務、積木化架構」,
你之後擴充 Orders、LINE、Redis 都可以依這個模式複製。


🧱 產品模組完整目錄(請先建立)

src/products/ 建:

1
2
3
4
5
6
7
products/
├─ dto/
│ ├─ create-product.dto.ts
│ └─ update-product.dto.ts
├─ products.controller.ts
├─ products.service.ts
└─ products.module.ts

———————————————————-

📌 1. products.module.ts

———————————————————-

1
2
3
4
5
6
7
8
9
import { Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';

@Module({
controllers: [ProductsController],
providers: [ProductsService],
})
export class ProductsModule {}

這是「積木」—— module 是可獨立拆出部署的最小單位。


———————————————————-

📌 2. products.service.ts

———————————————————-

目前為 in-memory 模式(第一塊積木)
之後會改 Prisma 連 MySQL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Injectable()
export class ProductsService {
private products = [];
private nextId = 1;

findAll() {
return this.products;
}

findOne(id: number) {
const product = this.products.find((p) => p.id === id);
if (!product) {
throw new NotFoundException(`Product #${id} not found`);
}
return product;
}

create(dto: CreateProductDto) {
const newProduct = {
id: this.nextId++,
name: dto.name,
price: dto.price,
description: dto.description ?? '',
};

this.products.push(newProduct);
return newProduct;
}

update(id: number, dto: UpdateProductDto) {
const product = this.findOne(id);
Object.assign(product, dto);
return product;
}

remove(id: number) {
const index = this.products.findIndex((p) => p.id === id);
if (index === -1) throw new NotFoundException('Product not found');

this.products.splice(index, 1);
return { success: true };
}
}

———————————————————-

📌 3. products.controller.ts

———————————————————-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
ParseIntPipe,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}

@Get()
findAll() {
return this.productsService.findAll();
}

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.productsService.findOne(id);
}

@Post()
create(@Body() dto: CreateProductDto) {
return this.productsService.create(dto);
}

@Patch(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateProductDto,
) {
return this.productsService.update(id, dto);
}

@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.productsService.remove(id);
}
}

———————————————————-

📌 4. create-product.dto.ts

———————————————————-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { IsNotEmpty, IsNumber, IsOptional, IsString, Min } from 'class-validator';

export class CreateProductDto {
@IsString()
@IsNotEmpty()
name: string;

@IsNumber()
@Min(0)
price: number;

@IsOptional()
@IsString()
description?: string;
}

———————————————————-

📌 5. update-product.dto.ts

———————————————————-

1
2
3
4
import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';

export class UpdateProductDto extends PartialType(CreateProductDto) {}

這樣你就不需要重寫欄位,完全符合 DRY 原則。


———————————————————-

🚀 如何啟動並測試(你現在就能用)

———————————————————-

啟動:

1
npm run start:dev

測試 API(Postman / Thunder Client):

1️⃣ GET 所有產品

1
GET http://localhost:3000/products

2️⃣ 新增產品

1
2
3
4
5
6
7
8
POST http://localhost:3000/products
Content-Type: application/json

{
"name": "綠茶",
"price": 35,
"description": "清爽無糖"
}

3️⃣ 查詢單一產品

1
GET http://localhost:3000/products/1

4️⃣ 更新

1
2
3
4
PATCH http://localhost:3000/products/1
{
"price": 40
}

5️⃣ 刪除

1
DELETE http://localhost:3000/products/1

🎉 恭喜!

你已經完成:

  • 第一塊積木(Products Module)
  • 正確的 NestJS 模組化架構
  • DTO + Validation
  • Controller / Service 分層
  • DI 注入
  • In-memory CRUD(後面換成 MySQL 只需更改 service)

之後 A~G 階段都會基於這塊積木擴充下去。


🎯 下一步你想做哪一個?

A. 我要把 ProductsModule 換成連 MySQL / Prisma

→ 完整程式碼我可立即提供

B. 我要做第二塊積木:OrdersModule(含 order_items)

→ 直接生成完整 CRUD

C. 我要開始做 LINE Webhook 模組(階段 C)

→ 完整 webhook + replies + 事件 parser

D. 幫我畫這個 NestJS 專案的架構圖(後端專用)

選一個,我立即開始。