CHAPTER 10 / DATABASE

資料庫:
把東西存起來

使用者註冊了、買了、留言了——這些資料都要存。存哪?檔案不夠用、變數一重整就沒。 這章教你 SQL(PostgreSQL/Supabase)、NoSQL(MongoDB)、ORM(Prisma),上完能撐住一個 SaaS。

  • 會設計資料表、寫 SQL 查詢
  • 會用 Supabase 做出有後台的 SaaS
  • 會用 MongoDB 處理彈性結構資料
  • 會用 Prisma ORM 操作資料庫
  • 分得清何時用 SQL、何時用 NoSQL
LESSON 10.1

SQL vs NoSQL

SQL(PostgreSQL)NoSQL(MongoDB)
結構表格、欄位固定文件、欄位彈性
關聯強大(JOIN)較弱
一致性最終一致
學習較難(要會 SQL)較易(像 JSON)
適合電商、訂單、財務內容、社群、日誌

不確定?選 PostgreSQL。它支援 JSON 欄位(兼具 NoSQL 彈性)、能勝任 99% 場景。Supabase、Neon 都是 PostgreSQL 雲端服務。

LESSON 10.2

SQL 入門

建表

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  name VARCHAR(100),
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  body TEXT,
  published BOOLEAN DEFAULT FALSE
);

CRUD

-- 新增
INSERT INTO users (email, name) VALUES ('a@b.com', '小明');

-- 查詢
SELECT * FROM users WHERE id = 1;
SELECT name, email FROM users ORDER BY created_at DESC LIMIT 10;

-- JOIN(最強功能)
SELECT p.title, u.name
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.published = TRUE;

-- 統計
SELECT user_id, COUNT(*) AS post_count
FROM posts
GROUP BY user_id
HAVING COUNT(*) > 5;

-- 更新
UPDATE users SET name = '阿明' WHERE id = 1;

-- 刪除
DELETE FROM posts WHERE id = 42;

索引:查詢加速

CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_created ON posts(created_at DESC);

沒索引的大表查詢慢到要哭。常用 WHERE / ORDER BY 的欄位都該有索引。

LESSON 10.3

Supabase:PostgreSQL 雲端神器

Supabase = 託管 PostgreSQL + 即時訂閱 + 認證 + 檔案儲存 + Auto-API。個人專案、SaaS MVP 神器。免費方案夠用。

三步驟上線

  1. 到 supabase.com 建專案,挑離你近的區域(東京)
  2. SQL Editor 建表、設 RLS(Row Level Security)
  3. 前端裝 @supabase/supabase-js 直連
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

// 查
const { data, error } = await supabase
  .from('posts')
  .select('*, user:users(name)')
  .eq('published', true)
  .order('created_at', { ascending: false })
  .limit(10);

// 寫
await supabase.from('posts').insert({ title: '你好', user_id: 1 });

// 即時訂閱
supabase.channel('posts')
  .on('postgres_changes', { event: 'INSERT', table: 'posts' }, payload => {
    console.log('有新貼文!', payload.new);
  })
  .subscribe();

RLS(Row Level Security):超重要

-- 開 RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- 大家可以看 published 文章
CREATE POLICY "public read" ON posts
  FOR SELECT USING (published = true);

-- 只能改自己的
CREATE POLICY "own update" ON posts
  FOR UPDATE USING (auth.uid() = user_id);

用 Supabase 一定要設 RLS。Anon key 是公開的,不設 RLS 等於資料庫對全世界開放。新手最常踩的雷。

LESSON 10.4

Prisma ORM

ORM = 用程式語法操作資料庫,不用直接寫 SQL。Prisma 是 Node.js 最強。

npm install prisma @prisma/client
npx prisma init
// schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

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

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  posts Post[]
}

model Post {
  id       Int    @id @default(autoincrement())
  title    String
  user     User   @relation(fields: [userId], references: [id])
  userId   Int
}
npx prisma migrate dev --name init
npx prisma studio  # 視覺化資料庫操作
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

// 查(含關聯)
const posts = await prisma.post.findMany({
  where: { published: true },
  include: { user: true },
  orderBy: { createdAt: 'desc' },
  take: 10
});

// 建
await prisma.post.create({
  data: { title: '你好', user: { connect: { id: 1 } } }
});
LESSON 10.5

MongoDB

NoSQL 王者。資料像 JSON,schema 彈性。Insight 後台、即時通訊、IoT 資料常用。

// 用 Mongoose
import mongoose from 'mongoose';

const postSchema = new mongoose.Schema({
  title: { type: String, required: true },
  body: String,
  tags: [String],
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  meta: { type: mongoose.Schema.Types.Mixed }   // 任意 JSON
}, { timestamps: true });

const Post = mongoose.model('Post', postSchema);

// 用
await Post.create({ title: '你好', tags: ['tech'] });
const posts = await Post.find({ tags: 'tech' }).populate('author');

MongoDB Atlas(雲端託管,免費)

到 mongodb.com/atlas 建免費 cluster,把連線字串放 .env 就能用。Insight 早期就跑在 Atlas 免費版。

LESSON 10.6

資料表設計三原則

  1. Normalization(正規化):每樣東西只存一次。使用者資料只在 users 表,posts 表存 user_id。
  2. 關聯:1對多用外鍵、多對多用中間表。
  3. 命名一致:表名複數(users)、外鍵 {單數}_id。團隊一致就好。

多對多範例

-- 文章可以有多個標籤、標籤可以在多篇
CREATE TABLE posts ( id SERIAL PRIMARY KEY, ... );
CREATE TABLE tags ( id SERIAL PRIMARY KEY, name VARCHAR );
CREATE TABLE post_tags (
  post_id INT REFERENCES posts(id),
  tag_id INT REFERENCES tags(id),
  PRIMARY KEY (post_id, tag_id)
);

練習:多人部落格資料庫

  1. 用 Supabase 建 users、posts、comments 三張表
  2. 用 Prisma 寫 schema,跑 migrate
  3. RLS:使用者只能編輯自己的文章 / 留言
  4. API:列文章(含作者名、留言數)
  5. 用 Supabase realtime,新留言即時推播給文章作者

常見卡關 FAQ

// DB 學員最常問的問題
Supabase Free 夠用嗎?
個人專案、MVP 完全夠。500MB 資料、50000 月活、2GB 頻寬。超過再升級 25 美/月。比自己架便宜太多。
Prisma 還是直接寫 SQL?
中小專案 Prisma 香爆——type-safe、自動 migration、有 Studio。只在做極致效能(高併發 query)才直接寫 SQL。
N+1 query 問題是什麼?
查 10 篇文章,再為每篇查作者 = 11 次 query。應該用 JOIN(include 在 Prisma、populate 在 Mongoose),一次撈完。Slack 變慢通常是這個。
怎麼避免 SQL 注入?
永遠用參數化查詢,不要字串拼接。Prisma、Supabase、Mongoose 都自動處理。手寫 raw SQL 才要小心。詳見第 13 章。
資料庫怎麼備份?
Supabase / Atlas 自動每日備份。自架 PostgreSQL 用 pg_dump 寫 cron 排程。關鍵:要驗證能還原,沒測過的備份等同沒備份。
← 上一章
10 後端