今天會開始進行專案初始化的部分,包含專案的初始架構與資料庫的初始化。
這次計劃使用 Next.js 來完成整個全端專案,因此直接使用官方腳本來完成專案的初始化。
npx create-next-app@latest copygram --yes
執行完成後,會得到以下的專案結構:
copygram/
├── src/
│ └── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── public/
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
├── eslint.config.mjs
├── next.config.ts
├── next-env.d.ts
├── package.json
├── package-lock.json
├── postcss.config.mjs
├── README.md
├── tsconfig.json
└── node_modules/
這是一個使用 TypeScript 和 Tailwind CSS 的標準 Next.js 15 專案結構,採用 App Router 架構。
為了刻意練習,這次使用不熟悉的 PostgreSQL 作為資料庫,並搭配 Drizzle ORM。
PostgreSQL 是目前相當受歡迎的開源關聯式資料庫,以穩定性與強大功能著稱,支援豐富的 SQL 特性(像是 transaction、window function、JSONB、全文檢索)。它不只適合一般 CRUD,也能應付複雜查詢與高可靠度的系統。
Drizzle ORM 則是一個型別安全的 TypeScript ORM,可以從資料表 schema 自動推導型別,讓程式碼與資料庫結構緊密結合。另外 Drizzle 在語法上緊貼 SQL 標準,讓開發者可以更直覺地寫出 SQL 語句,從而提供更直覺、更低學習成本的開發體驗。
直接透過 PostgreSQL 官方網站 下載安裝包,並按照指示安裝。
使用官方 pgAdmin 在本地服務中建立 'copygram' 資料庫。
根據官網的安裝指南,安裝 Drizzle。
npm i drizzle-orm pg dotenv
npm i -D drizzle-kit tsx @types/pg
在專案根目錄建立 .env
檔案,並設定 DATABASE_URL
。
DATABASE_URL=postgresql://<username>:<password>@<host>:<port>/<database-name>
Drizzle 提供了單一連線或是連線池的連線方式,因為在實際應用中,資料庫連線的建立與關閉都需要成本。如果每一次 API 請求都重新建立連線,效能會大幅下降,甚至導致資料庫資源耗盡。使用連線池(connection pool)可以預先建立好一批連線並重複使用,避免頻繁建立與銷毀連線所帶來的負擔,並且能控制最大連線數,避免流量尖峰時把資料庫打爆。
src/lib/db/index.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";
// 創建 PostgreSQL 連接池
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
// 創建 Drizzle 實例使用連接池
export const db = drizzle(pool, { schema });
// 導出 pool 供直接使用(如果需要)
export { pool };
// 關閉連接池的函數(通常用於應用程序關閉時)
export const closeDb = async () => {
await pool.end();
};
src/lib/db/schema.ts
import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod"; // 使用 drizzle-zod 來生成 Zod schema
// 示例用戶表
export const users = pgTable("users", {
id: uuid("id").defaultRandom().primaryKey(),
email: varchar("email", { length: 255 }).notNull().unique(),
password: varchar("password", { length: 255 }).notNull(),
name: varchar("name", { length: 255 }).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// Zod schemas for validation
export const insertUserSchema = createInsertSchema(users);
export const selectUserSchema = createSelectSchema(users);
// 類型導出
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
drizzle.config.ts
import { defineConfig } from "drizzle-kit";
import "dotenv/config";
export default defineConfig({
schema: "./src/lib/db/schema.ts",
out: "./scripts/db/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
首先透過指令測試連線是否正確
npx drizzle-kit push
如果連線成功,會看到以下的訊息:
No config path provided, using default 'drizzle.config.ts'
Reading config file '/Users/femou/Projects/copygram/drizzle.config.ts'
postgresql://postgres:password@localhost:5432/copygram
Using 'pg' driver for database querying
[✓] Pulling schema from database...
[✓] Changes applied
成功後再透過指令生成 migration 檔案
npx drizzle-kit generate
如果生成成功,會看到類似以下的訊息:
No config path provided, using default 'drizzle.config.ts'
Reading config file '/Users/femou/Projects/copygram/drizzle.config.ts'
postgresql://postgres:password@localhost:5432/copygram
1 tables
users 6 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ scripts/db/migrations/0000_noisy_avengers.sql 🚀
成功後執行 migration 檔案
npx drizzle-kit migrate
如果執行成功,打開 pgAdmin 可以看到 'Databases/copygram/Schemas/Tables' 中出現 'users' 資料表。
scripts/db/seed.ts
import "dotenv/config";
import { db, closeDb } from "../../src/lib/db";
import { type NewUser, users } from "../../src/lib/db/schema";
async function seed() {
console.log("🌱 開始 seeding...");
try {
// 清空現有數據
await db.delete(users);
console.log("✅ 清空現有用戶數據");
// 插入種子數據
const seedUsers: NewUser[] = [
{
email: "admin@copygram.com",
name: "Admin User",
password: "password",
},
{
email: "user@copygram.com",
name: "Regular User",
password: "password",
},
];
await db.insert(users).values(seedUsers);
console.log("✅ 插入種子用戶數據");
console.log("🎉 Seeding 完成!");
} catch (error) {
console.error("❌ Seeding 失敗:", error);
process.exit(1);
} finally {
await closeDb();
}
}
seed();
完成後執行種子資料
npx tsx scripts/db/seed.ts
完成後回到 pgAdmin,使用 query 查看資料表
SELECT * FROM users;
可以看到我們的種子資料已經被插入到資料庫中。
至此完成專案與資料庫的初始架構如下:
copygram/
├── src/
│ ├── app/
│ │ ├── favicon.ico
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── lib/
│ └── db/
│ ├── index.ts # 數據庫連接配置 (使用 Pool)
│ └── schema.ts # 數據庫 schema 定義
├── scripts/
│ └── db/
│ ├── migrations/ # migration 文件目錄
│ │ ├── 0000_noisy_avengers.sql
│ │ └── meta/
│ │ ├── 0000_snapshot.json
│ │ └── _journal.json
│ └── seed.ts # 種子數據腳本
├── public/
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
├── drizzle.config.ts # Drizzle 配置文件
├── .env.local # 環境變量 (需要手動創建)
├── eslint.config.mjs
├── next.config.ts
├── next-env.d.ts
├── package.json
├── package-lock.json
├── postcss.config.mjs
├── README.md
├── tsconfig.json
└── node_modules/
相較於初始的 Next.js 專案,新增了:
src/lib/db/
目錄:包含數據庫連接與 schema 定義scripts/db/
目錄:包含數據庫操作腳本與 migration 文件drizzle.config.ts
:Drizzle 工具配置.env.local
:環境變量配置檔案