PrismaORM資料庫TypeScript後端開發前端教學

Prisma ORM 教學:前端工程師資料庫入門指南

不用會 SQL!用 TypeScript 操作資料庫,Prisma ORM 完整教學。含 Schema 定義、CRUD 操作、Express 整合實戰,適合前端工程師的資料庫入門。

· 6 分鐘閱讀

想把 Express 加上資料庫,卻被 SQL 的 INSERT INTO、JOIN、foreign key 搞得一個頭兩個大?Prisma 讓你用 TypeScript 的語法操作資料庫,學一套工具就能涵蓋大多數常見的資料庫操作需求。這篇整理 Prisma 的核心概念、Schema 定義與 CRUD 操作實務。


前言:為什麼要學 ORM?

傳統上,Node.js 連接資料庫需要寫 SQL:

INSERT INTO users (name, email) VALUES ('小明', 'xiaoming@example.com');
SELECT * FROM users WHERE email = 'xiaoming@example.com';

SQL 功能強大,但缺點也很明顯:

  • 語法瑣碎:大小寫、空格、單引號都要注意,一個小錯誤就debug半天
  • 沒有型別檢查:寫錯欄位名稱,SQL 不會告訴你,要等到 query 執行失敗才知道
  • 跨資料庫語法不同:MySQL 和 PostgreSQL 的語法有些許差異,換資料庫等於重新學

ORM(Object-Relational Mapping)的思路是:用程式語言(TypeScript)來描述資料結構和操作,而不是用 SQL 字串。Prisma 是目前 Node.js/TypeScript 生態中最受歡迎的 ORM 之一。


Prisma 的核心組成

Prisma 分三層:

  1. Prisma Schema:用一個 schema.prisma 檔案定義資料庫結構(相當於 SQL 的 CREATE TABLE)
  2. Prisma Client:一個型別安全的 TypeScript SDK,用於在程式碼中操作資料庫
  3. Prisma Migrate:根據 Schema 自動產生資料庫遷移腳本

第一步是寫 schema.prisma

// schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"   // 開發環境用 SQLite,之後可以換成 postgresql、mysql 等
  url      = env("DATABASE_URL")
}

// 定義 User 模型
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique   // email 不可重複
  createdAt DateTime @default(now())
  
  // 關聯:一個使用者可以有多筆訂單
  orders    Order[]
}

// 定義 Order 模型
model Order {
  id        Int      @id @default(autoincrement())
  amount    Int
  status    String   @default("pending")  // pending / paid / cancelled
  
  // 關聯:每筆訂單屬於一個使用者
  userId    Int
  user      User     @relation(fields: [userId], references: [id])
  
  createdAt DateTime @default(now())
}

這段 Schema 翻譯成白話:

  • User:有 id、name、email、createdAt,一個 User 可以有多筆 Order
  • Order:有 id、amount、status、createdAt,每筆 Order 屬於一個 User

@id 標記主鍵,@default(autoincrement()) 自動遞增,@unique 代表不可重複——這些標記的語意直觀,不需要懂 SQL 語法細節

初始化 Prisma

# 安裝 Prisma CLI 與 Client
npm install prisma --save-dev
npm install @prisma/client

# 在專案根目錄初始化 Prisma
npx prisma init

初始化完成後,Prisma 會在 prisma/schema.prisma 產生 Schema 檔案,並在 .env 產生 DATABASE_URL 設定。


Prisma Client:用 TypeScript 操作資料庫

設定好 Schema 之後,執行 npx prisma generate,Prisma 會根據你的 Schema 自動產生一個型別安全的 Client,讓你用 TypeScript 操作資料庫。

基本 CRUD 操作

// lib/prisma.ts
// 建立一個 Prisma Client 實例(整個專案共用一個)
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
export default prisma;
// 插入一筆新使用者(相當於 INSERT INTO)
const newUser = await prisma.user.create({
  data: {
    name: '小明',
    email: 'xiaoming@example.com',
  },
});
console.log(`Created user: ${newUser.name}, ID: ${newUser.id}`);
// 查詢所有使用者(相當於 SELECT * FROM users)
const users = await prisma.user.findMany();
users.forEach(user => {
  console.log(`${user.id}: ${user.name} (${user.email})`);
});
// 條件查詢:找出特定使用者(相當於 SELECT * FROM users WHERE email = ?)
const user = await prisma.user.findUnique({
  where: { email: 'xiaoming@example.com' },
});
if (user) {
  console.log(`Found: ${user.name}`);
}
// 更新使用者(相當於 UPDATE users SET name = ? WHERE id = ?)
const updatedUser = await prisma.user.update({
  where: { id: 1 },
  data: { name: '大明' },
});
// 刪除使用者(相當於 DELETE FROM users WHERE id = ?)
await prisma.user.delete({
  where: { id: 1 },
});

關聯查詢:一次拿到訂單和使用者

這是 Prisma 最強大的地方——用 TypeScript 語法處理 SQL JOIN

// 找出所有使用者,順便拿到每個使用者的訂單
const usersWithOrders = await prisma.user.findMany({
  include: {
    orders: true,  // 包含該使用者的所有訂單
  },
});

usersWithOrders.forEach(user => {
  console.log(`${user.name} 的訂單:`);
  user.orders.forEach(order => {
    console.log(`  - 訂單 ${order.id}:$${order.amount} (${order.status})`);
  });
});
// 查詢特定使用者的訂單,並篩選已付款的
const paidOrders = await prisma.order.findMany({
  where: {
    userId: 1,
    status: 'paid',  // 只取已付款的訂單
  },
  orderBy: {
    createdAt: 'desc',  // 依時間倒序
  },
});

Prisma + Express 實戰

把 Prisma 接入 Express,把之前 RESTful API 教的知識用上來:

// server-with-db.mjs
import express from 'express';
import { PrismaClient } from '@prisma/client';

const app = express();
const prisma = new PrismaClient();

app.use(express.json());

// GET /api/users — 取得所有使用者
app.get('/api/users', async (req, res) => {
  const users = await prisma.user.findMany({
    include: { orders: true },  // 包含訂單數量
  });
  res.json(users);
});

// GET /api/users/:id — 取得特定使用者
app.get('/api/users/:id', async (req, res) => {
  const user = await prisma.user.findUnique({
    where: { id: parseInt(req.params.id) },
    include: { orders: true },
  });
  
  if (!user) {
    return res.status(404).json({ error: '使用者不存在' });
  }
  res.json(user);
});

// POST /api/users — 建立新使用者
app.post('/api/users', async (req, res) => {
  const { name, email } = req.body;
  
  if (!name || !email) {
    return res.status(400).json({ error: 'name 和 email 是必填欄位' });
  }
  
  try {
    const newUser = await prisma.user.create({
      data: { name, email },
    });
    res.status(201)
      .set('Location', `/api/users/${newUser.id}`)
      .json(newUser);
  } catch (err) {
    // Prisma 在 email @unique 衝突時會丟出 P2002 錯誤碼
    if (err.code === 'P2002') {
      return res.status(409).json({ error: '這個 email 已經被註冊了' });
    }
    console.error('Unexpected error:', err);
    res.status(500).json({ error: '伺服器錯誤' });
  }
});

// DELETE /api/users/:id
app.delete('/api/users/:id', async (req, res) => {
  try {
    await prisma.user.delete({
      where: { id: parseInt(req.params.id) },
    });
    res.status(204).end();
  } catch (err) {
    if (err.code === 'P2025') {
      return res.status(404).json({ error: '使用者不存在' });
    }
    console.error('Unexpected error:', err);
    res.status(500).json({ error: '伺服器錯誤' });
  }
});

app.listen(3000, () => {
  console.log('Server with Prisma running on http://localhost:3000');
});

為什麼前端工程師該選 Prisma?

跟其他 ORM 比起來(Sequelize、TypeORM),Prisma 的優勢:

  • Schema 即文件schema.prisma 本身就是資料庫結構的完整文件,團隊成員一看就懂
  • Migration 簡單:修改 Schema 後執行 npx prisma migrate dev,Prisma 自動產生 SQL 遷移腳本
  • 錯誤碼友善:Prisma 有完整的錯誤碼體系,P2002 是 unique 衝突、P2025 是找不到記錄——這些錯誤碼讓除錯有方向
  • 型別安全:Prisma Client 是根據你的 Schema 自動產生的,Query 結果有完整的 TypeScript 型別

常見問題

Q:Prisma 效能比其他 ORM 差嗎?

A:Prisma Client 產生的 query 已經過高度優化,在多數網頁應用的規模下(幾千到幾萬筆資料)效能差異感受不到。如果真的遇到效能瓶頸,Prisma 也支援Raw Query,讓你在必要時直接寫 SQL。

Q:Prisma 支援哪些資料庫?

A:PostgreSQL、MySQL、SQLite、SQL Server、MongoDB(預覽)。開發階段推薦用 SQLite——不需要另外安裝資料庫服務,一個檔案就能跑,團隊成員之間分享容易。

Q:什麼時候該上正式資料庫?

A:當 SQLite 開始遇到以下情況時,就是換正式資料庫的時機:

  • 需要多人同時寫入(SQLite 寫入是單執行緒)
  • 需要備援機制
  • 資料量開始接近 1GB
  • 需要更嚴格的 Transaction 支援

總結:從 SQL 字串地獄解脫

Prisma 的核心價值:用你已經會的 TypeScript 語法操作資料庫,不需要另外學 SQL 語法細節

這篇文章涵蓋的範圍:

  • Schema 定義:描述資料結構
  • Prisma Client:插入、查詢、更新、刪除
  • 關聯查詢:一次拿到相關資料
  • 實際 Express 整合

下一步,建議把 Prisma + Express 的組合實際跑起來,用 SQLite 當開發資料庫,做一個完整的 CRUD API 出來。資料庫的學習最有效的方式是實作——看再多文件,都不如親手建一個、壞一個、修一個。


本篇是「前端工程師後端入門」系列第三篇。系列前兩篇:《從 Node.js 原生 HTTP 到 Express 框架》、《RESTful API 設計:前端視角的 API 思維》。