下面我會直接給你可立即貼進你的 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 原則。
———————————————————-
🚀 如何啟動並測試(你現在就能用)
———————————————————-
啟動:
測試 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 專案的架構圖(後端專用)
選一個,我立即開始。