---
title: Nuxt + Prisma + PostgrelSQL 全端專案
---
# Nuxt + Prisma + PostgrelSQL 全端專案
本專案是一個以 Nuxt 3 為前端、Prisma + PostgreSQL 為後端的電商網站,並整合 TailwindCSS、Pinia 狀態管理與 JWT 身份驗證,實現完整的會員註冊、商品購物、訂單流程與後台管理功能。
---
## 🔧 專案架構
- **MyShop/**
- frontend/ # 使用 Vite + Vue3 建構的購物網站前台
- admin/ # 使用 Nuxt3 開發的後台管理系統
- prisma/ # Prisma schema 與 migration 設定
- .env # 資料庫連線設定
---
## 🖥️ 前端(Frontend)
- **框架**:Vue 3 + Vite
- **狀態管理**:Pinia(含購物車狀態持久化)
- **路由**:Vue Router
- **樣式**:Tailwind CSS + Element Plus(UI 套件)
- **功能**:
- 商品列表與詳細頁面
- 加入購物車與移除功能
- 結帳功能,含庫存數量驗證
- 訂單送出後導向訂單詳情頁
---
## 🛠️ 後台(Admin)
- **框架**:Nuxt 3
- **UI 套件**:Element Plus + Tailwind CSS
- **資料存取**:透過 API 與 Prisma 操作 PostgreSQL 資料庫
- **功能模組**:
- 商品管理(新增 / 編輯 / 刪除 / 庫存設定)
- 訂單管理(查看訂單、訂單詳情)
- 管理員登入 / 登出
- 管理員管理 (新增、編輯)
---
## 🗄️ 資料庫
- **類型**:PostgreSQL(部署於 Render)
- **ORM**:Prisma ORM
- **Schema 架構**:
- `Product`:商品資訊與庫存數量
- `Order`:訂單總覽資料
- `OrderItem`:訂單中的每一筆商品與數量明細
- `User`: 會員
- `Admin`: 管理員
---
# 🌐 Nuxt 是什麼?
Nuxt.js(通常簡稱為 Nuxt)是一個基於 Vue.js 的高階框架,用來構建 現代化 Web 應用程式。它幫助開發者更容易地實作:
1. 伺服器端渲染(SSR)
2. 靜態網站生成(SSG)
3. 單頁應用(SPA)
4. 全端應用(Fullstack)
## 💡 核心特點
#### 功能 說明
✅ SSR(Server-Side Rendering) 頁面由伺服器預先渲染,對 SEO 友好,載入速度快
✅ 靜態網站生成(SSG) 預先生成 HTML,可部署到 CDN 上,適合部落格、官網等
✅ 自動路由 pages/ 資料夾自動變成對應的路由
✅ 全端支援 可寫 API:server/api/*.ts 是後端邏輯,整合 Prisma、DB 很方便
✅ 強化 DX(開發體驗) Hot reload、TypeScript 支援、Composable API(setup 語法)
✅ 模組系統 如 auth、i18n、tailwind、pinia、image 等模組一行裝起
## 🛠 Nuxt 的用途
### 1. 建立 SEO 友善的網站
像是: 部落格、電商網站、公司介紹頁面。
因為 SSR 可以讓搜尋引擎更容易抓到內容。
### 2. 開發 Fullstack 應用
Nuxt 3 提供 server API 支援,例如你可以使用:
/server/api 目錄撰寫後端 API
搭配 Prisma 使用資料庫
使用 JWT 驗證登入系統
👉 一個 Nuxt 專案就可以包辦前後端,非常適合開發管理後台、會員系統、電商平台等。
### 3. 建立靜態網站(預渲染)
適合一次編譯後部署上 CDN,像是: 部落格、產品介紹頁、 技術文件
使用 `nuxt generate` 指令即可。
### 4. 做為 Vue 應用的升級版
你如果本來會寫 Vue,只要學 Nuxt 的額外功能,馬上能上手:
頁面路由 = pages 資料夾
中介層(middleware)
插件系統(plugins)
狀態管理(Pinia)
API server(server/api)
---
## Nuxt 畫面架構說明
畫面的整體結構主要由 Header、Sidebar 與 MainContent 三大區塊組成。

在這個結構中,**會根據路由變化的部分僅有 MainContent 區塊。**
在 Nuxt 中,我們可以透過頁面加入以下程式碼,來指定該頁面所使用的 Layout 以及 Middleware:
```
definePageMeta({
layout: 'admin',
middleware: ['auth']
});
```
加入上述程式碼後,該頁面就會預設使用 `admin` 這個 Layout,並且套用名為 `auth` 的中介層驗證機制。
---
### Layout 插槽機制
如下圖,`<slot />` 代表的是可替換內容的插槽,Nuxt 會根據當前的路由,自動渲染對應頁面的內容到這個位置:

這樣的架構讓開發者能夠有效地拆分畫面、重用結構,並在不同頁面中注入各自的內容,維持一致的 UI 外框設計。
---
### 全域管理(Pinia + persist)
在Vue當中會使用**Pinia**來做全域狀態管理,搭配 **persist**插件,把需要的狀態儲存到**localstorge**。
在Nuxt中當然也可以使用。只是有些設定上的差異性。
### Vue
main.js
```
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
app.use(pinia);
```
### Nuxt
nuxt.config.ts
```
import { resolve } from 'path';
export default defineNuxtConfig({
modules: [
'@nuxtjs/tailwindcss',
'@pinia/nuxt',
'pinia-plugin-persistedstate/nuxt'
]
});
```
plugins/piniaPersist.client.ts
```
import { defineNuxtPlugin } from '#app'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import type { Pinia } from 'pinia'
export default defineNuxtPlugin((nuxtApp) => {
(nuxtApp.$pinia as Pinia).use(piniaPluginPersistedstate)
})
```
---
在Nuxt中使用**pinia**,需要注意的事
一般來說Vue中的template會類似下面圖示:

但是在Nuxt中會出下面圖示中的錯誤訊息,
**Hydration completed but contains mismatches.**

這個主要的原因是, Nuxt (或 Vue SSR) 在執行「Hydration」(將伺服器端渲染的 HTML 轉換為可互動的 Vue 組件)時,發現 伺服器端渲染出來的內容和客戶端接手的內容不一致。
### 為什麼會出現?
#### 1. 使用了 localStorage / window 等 browser-only API
在下面圖中,**currentAdmin**是 藉由pinia persist 自動從 localstorage存取的。
所以才導致 **Hydration completed but contains mismatches.** 出現這個錯誤訊息。


### 解決方法
#### 使用 <ClientOnly></ClientOnly> 包裹不需要SSR的部分。

### 💬 為什麼這樣就可以?
因為 **<ClientOnly>** 會跳過 SSR,那 SSR 階段根本不會產生這段 HTML,自然也就沒有 hydration 的錯誤。Vue 會在 CSR 階段動態載入它
---
## Middleware
#### 是一種在頁面渲染之前攔截請求並執行邏輯的機制,通常用來實作以下幾種功能:

#### 在這個專案中,我們使用了兩種類型的 Middleware,分別為:
- admin/middleware:屬於 Nuxt 的前端(client-side 或 universal)middleware,主要用於 路由守衛與驗證登入狀態。
- admin/server/middleware:屬於 Nuxt 的後端 Middleware(基於 Nitro server),用於處理例如 CORS 設定、Header 檢查、Token 驗證等伺服器層級邏輯。
如下圖所示,兩者在執行階段與角色上的差異一目了然:

#### 程式碼範例參考:
**admin/middleware/auth.ts(前端路由驗證 Middleware)**
```
import { useAuthStore } from '~/store/auth';
export default defineNuxtRouteMiddleware(() => {
if (process.server) return;
const authStore = useAuthStore();
if (!authStore.token || !authStore.admin) {
return navigateTo('/login');
}
});
```
**admin/server/middleware/cors.ts(伺服器端 Middleware)**
```
export default defineEventHandler((event) => {
setHeader(event, 'Access-Control-Allow-Origin', '*')
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
// 處理預檢請求
if (getMethod(event) === 'OPTIONS') {
return new Response(null, { status: 204 })
}
})
```
---
## Nuxt 中的 API 運作方式
Nuxt 3 採用了 **Nitro Server** 作為內建的後端伺服器架構,讓你可以 直接在 server/api/ 目錄下撰寫 API,無需額外設定 Express 或其他後端框架。
✅ 支援 REST / JSON / server functions。
✅ 可以存取 cookies、headers、query、body 等。
✅ 適合建構 SSR-friendly 的全端應用。
## 匹配 HTTP 請求方法 (HTTP Request Method)**
我們可以添加 .get、.post、.put 或 .delete 等檔案名稱後綴,來匹配對應的 HTTP request methods。
- /api/product/index.**get**.ts ---> 取得所有商品資料
- /api/product/index.**post**.ts ---> 新增商品資訊
- /api/product/[id].**get**.ts ---> 取得單一筆商品資訊
- /api/product/[id].**put**.ts ---> 更新單一筆商品資訊
- /api/product/[id].**delete**.ts ---> 刪除單一筆商品資訊
#### 建立第一個測試API
Nuxt 會自動掃描 server 目錄中的檔案結構,建立 Server API 時通常以 .js 或 .ts 作為副檔名,並依照官方建議,每個檔案都應該預設匯出 **defineEventHandler()** 函數,並在其 handler 內實作處理邏輯。
**先在 server/api 底下建立一個hello.ts**
```
export default defineEventHandler(() => {
return { msg: 'Hello, Nuxt API!' };
});
```
訪問http://localhost:3000/api, 會看見回傳的 JSON 資料。

使用VScode的插件**Thunder Client**測試POST Request,成功的話會出現 200 OK

# Prisma 是什麼?
Prisma 是一套現代化的 TypeScript ORM(Object-Relational Mapping)工具,可以讓你更簡潔、安全地與資料庫互動。
它支援多種資料庫,包括 PostgreSQL、MySQL、SQLite、SQL Server 等。
✅ Prisma = 強型別 + 自動補全 + SQL 抽象化 + DB Schema 管理工具!
## 🎯 Prisma 的用途
✅ 對資料庫進行 CRUD 操作(Create / Read / Update / Delete)
✅ 使用 TypeScript 提供自動補全與型別檢查
✅ 管理資料庫 Schema(使用 Prisma Migrate)
✅ 與 REST API / GraphQL 無縫整合
✅ 提供 DB client,不需手寫 SQL
## 🧠 Prisma 的運作流程
### Prisma 的基本流程分成 3 個部分:
1️⃣ Schema 定義(prisma/schema.prisma)
你用類似 SQL 的語法定義資料表與關聯:
```
model Product {
id Int @id @default(autoincrement())
name String
description String
price Float
stock Int
orderItems OrderItem[]
createdAt DateTime @default(now())
}
```
### Prisma Client 自動產生
執行以下指令來產生 Prisma Client:
```
npx prisma generate
```
Prisma 會根據你定義的 schema 自動生成可用的 TypeScript 資料庫操作方法:
```
const users = await prisma.product.findMany();
```
你會得到:
- 型別保護(不會打錯欄位)
- 自動補全(更快開發)
- 避免 SQL injection
3️⃣ Migrate 管理資料庫
使用 npx prisma migrate 自動同步 Prisma schema 到你的資料庫:
```
npx prisma migrate dev --name init
```
這會產生:
- 一份 migration SQL 檔案

- 自動更新資料庫結構
- 確保團隊協作時 schema 一致
## 常用 CRUD 操作指令(以 product 為例)
### 1. 取得單筆資料(findUnique)
```
const product = await prisma.product.findUnique({ where: { id } });
```
### 2. 取得多筆資料(findMany)
```
const product = await prisma.product.findMany();
```
### 3. 條件搜尋(帶過濾條件)
```
const product = await prisma.product.findMany({
where: {
price: 10
},
});
```
### 4. 新增資料(create)
```
const product = await prisma.product.create({
data: {
name: body.name,
price: body.price,
stock: body.stock,
description: body.description || ''
}
});
```
### 5. 修改資料(update)
```
const updated = await prisma.product.update({
where: { id },
data: {
name: body.name,
price: body.price,
stock: body.stock,
description: body.description
}
});
```
### 6. 刪除資料(delete)
```
prisma.product.delete({ where: { id } })
```
## 部署之前確認完成執行下面程式碼
```
npx prisma migrate dev --name xxx
```
```
npx prisma migrate deploy
```
最後確認 migration 狀態
```
npx prisma migrate status
```
最後應該會顯示
```
1 migration found in prisma/migrations
Database schema is up to date!
```
# 部署到Vercel
#### 1. 註冊Vercel,直接使用GitHub登入
#### 2.新增部署專案,點擊**Add New**,選擇「 Project 」

#### 3.選擇GitHub倉庫中,你要部署的專案。點擊 「 Import 」

#### 4.進入畫面後,可以自訂Project Name,會根據你的 Project Name產生對應的網址。

如果你的專案中有2個子專案要分別部署的話,點擊Edit選擇目前想要部署的專案。

點擊後可以看到子專案有哪些,選擇你要的子專案。Vercel會直接辨識你的專案類型。

最後按**Deploy**,讓專案開始部署吧!

部署成功後,你可以在 Vercel 專案的 Domains 區塊中看到對應的網址。
接下來就可以把這個網址分享給其他人,讓大家可以瀏覽你完成的網站囉!
如果你有在這個專案中部署 API,那這個網址也會成為你 API 的對外路徑,記得在前端或其他應用中使用這個網址來呼叫 API。
## Vercel的環境變數設定
### 點擊Setting > Environment Variables

### 新增環境變數
直接在下面圖中紅色框框輸入你的環境變數Key和Value,輸入後按下Save。

然後往下滑動,會看到你剛剛新增的環境變數。

## 部署Vercel可能會發生的錯誤
### 子專案中的Frontend,只是純靜態部署。
必須要在專案底下建立一個 vercel.json 來處理 rewrites
```
{
"rewrites": [
{ "source": "/(.*)", "destination": "/" }
]
}
```
這樣任何未知的路徑都會 fallback 到 /index.html,由前端框架接管路由。
否則無法正確處理「非根目錄」的路由,導致 **404 NOT_FOUND**。
### 如果是使用Nuxt SRR的話,確認nuxt.config.ts 有設定以下的程式碼
```
export default defineNuxtConfig({
ssr: true,
nitro: {
preset: 'vercel'
}
});
```
# 參考資料
[Nuxt 3 Server API 與 Nitro Engine]( https://ithelp.ithome.com.tw/articles/10301197)
[Nuxt](https://nuxt.com/docs/api)
[pinia](https://pinia.vuejs.org/ssr/nuxt.html)
[pinia-Persistedstate](https://prazdevs.github.io/pinia-plugin-persistedstate/zh/frameworks/nuxt.html)
# GitHub
[Shop](https://github.com/larryzeng123/larryShop)
# 專案網站
[前台購物頁面](https://larry-shop-frontend-tawny.vercel.app/)
[後台管理頁面](https://larry-shop-mauve.vercel.app/)
[GitHub](https://github.com/Larryz1215/larryShop)