
在前面的章節,我們除了一開始的基礎操作,隨後到一些 bun 特別的使用
今天開始我們要用 bun 開始做些專案,以及實戰,去展現出bun 本身的全端能力
當然建立全端前,我們先搞定後端的部分,我們會用 DrizzleORM 去完成一個簡單的後端系統
並且整合知名且這年頭最有名的資料庫 postgresSQL,配合 ORM 完成我們簡單的資料庫串接與
CRUD
仿間有非常多 ORM 選擇,像是 typeORM,Prisma ...etc 為何我這裡要用 DrizzleORM
當然每個 ORM 有它的優劣比較,沒有對錯,可以看我以下我整理的表格去評斷,個人三種都有嘗試過
但既然選擇 bun 就代表認同極致的效能了,我們希望我們產出的成果是速度極快的
| 項目 | Drizzle ORM | TypeORM | Prisma |
|---|---|---|---|
| 設計理念 | 輕量、貼近 SQL 的 Query Builder | 傳統 Active Record / Data Mapper ORM | Schema-first,強型別 ORM + 獨立 Query Engine |
| 型別安全 | 極佳,直接從 schema 推導型別 | 中等,需依賴 decorator 正確性 | 極佳,透過生成的 Prisma Client |
| Schema 定義方式 | TypeScript 程式碼定義(無需 codegen) | Class + Decorator(@Entity, @Column) |
專屬 schema.prisma DSL,需 generate |
| 執行方式 | 產生接近原生 SQL,無額外查詢引擎 | 產生 SQL,內建多種查詢策略 | 透過 Query Engine(Rust binary / WASM)轉譯 |
| 效能 | 接近原生 SQL,開銷最小 | 中等,功能多但相對重 | 中等,多一層 Engine 轉譯開銷 |
| Migration 工具 | drizzle-kit(輕量) |
內建 migration CLI | prisma migrate(成熟穩定) |
| 學習曲線 | 低(懂 SQL 就容易上手) | 中~高(需理解 decorator、Repository 模式) | 低~中(DSL 需另外學習) |
| 關聯查詢 (Relations) | 支援,但語法較貼近 SQL join | 支援豐富(Eager/Lazy loading) | 支援豐富,API 直覺(include) |
| Raw SQL 支援 | 原生支援極佳 | 支援,但較繞 | 支援($queryRaw),但非主軸 |
| 多資料庫支援 | PostgreSQL, MySQL, SQLite 等 | 支援多種(Postgres, MySQL, SQLite, MSSQL, Oracle...) | PostgreSQL, MySQL, SQLite, MongoDB, SQL Server 等 |
| Bundle Size / 依賴 | 非常輕量,無需額外 binary | 中等,依賴 reflect-metadata | 較重(需 Prisma Engine binary) |
| Edge / Serverless 支援 | 極佳(無 binary 依賴) | 較差(reflect-metadata 在部分環境有問題) | 需搭配 Data Proxy / Accelerate 才能在 Edge 使用 |
| 生態系與社群 | 成長中,較新 | 成熟,社群龐大(尤其 NestJS 生態) | 成熟,文件與工具鏈非常完整 |
| TypeScript 整合 | 一級公民,型別直接推導 | 良好,但依賴 decorator 設定 | 一級公民,型別由 codegen 產生 |
| 適合場景 | 需要極致效能、貼近 SQL、Edge 環境 | 大型企業應用、偏好 OOP/Repository 模式(如 NestJS) | 快速開發、重視 DX 與型別安全的專案 |
| 主要缺點 | 生態較新、進階功能(如 seed、studio)仍在完善 | 型別安全較弱、部分 API 設計較舊 | Query Engine 增加啟動延遲與 bundle size |
那本身是老牌工程師的各位,對於寫 SQL 的能力如果都不錯,那對於 DrizzleORM 本身的適應力就會非常好
這年頭基本上沒有不用 docker 快速起服務的道理,我們先用 docker 把一個 postgresSQL 在本地起起來
我們就用簡單的 docker-compose 去實現這件事
(筆者在寫這篇文的時候 postgres 19 目前剛好在 beta 階段,然後 18 已經出來,因此我會用 18 版做 demo)
以下 docker-comopse.yaml
services:
db:
image: postgres:latest
restart: always
ports:
- 5432:5432
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
PG_DATA: "/var/lib/postgresql/18/docker"
volumes:
- ./data:/var/lib/postgresql/18/docker
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB']
interval: 10s
timeout: 5s
retries: 5
這邊設置環境變數 .env
POSTGRES_USER="<你的 postgresUser>"
POSTGRES_PASSWORD="<你喜歡的密碼>"
POSTGRES_DB="<資料庫名稱>"
[踩雷小提醒] : 如果是 18 版之前可能會與我的 docker-compose 有些不同,
在 pg_data 我們通常為了讓資料 persistance (持久化) 會映射到 某個資料夾
像我這邊是 ./data 這邊,不過18 版之後預設位置有些改變,如果不確定我建議先移掉 PG_DATA 的環境變數(environment)
以及 volumes 然後看沒設定的狀況下他在哪個位置
可以用以下指令驗證
docker exec -it <你的 db Id> psql -U <你的postgres_user> -d <你的目標資料庫> -c "show data_directory"
這樣就可以知道預設資料大致上存放在哪,PG_DATA 改成 show data_direcotry 提供的就好
我們這就把資料庫起起來看看
docker-compose up -d
開心的專案後記得要安裝下列依賴
bun add drizzle-orm postgres
bun add -d drizzle-kit
我們先建立資料表的 schema : .src/db/schema.ts
import { pgTable, serial, text, timestamp, boolean, varchar } from 'drizzle-orm/pg-core';
export const todo = pgTable('todos', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
description: text('description'),
completed: boolean('completed').default(false).notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull()
});
// 型別推論
export type Todo = typeof todos.$inferSelect;
export type NewTodo = typeof todos.$inferInsert;
這個與建立 sql 的語法很像: 所以有一種賓至如歸的感受
以下範例SQL :
CREATE TABLE todos (
id SERIAL PRIMARY KEY NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
completed BOOLEAN DEFAULT false NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);
我們把資料庫連線建立在 ./src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const databaseName = process.env.POSTGRES_DB;
const username = process.env.POSTGRES_USER;
const password = process.env.POSTGRES_PASSWORD;
const client = postgres({
host: 'db',
port: 5432,
db: databaseName,
username,
password,
});
export const db = drizzle(client, { schema });
我們建立一個 drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
const databaseName = process.env.POSTGRES_DB;
const username = process.env.POSTGRES_USER;
const password = process.env.POSTGRES_PASSWORD;
const host = process.env.POSTGRES_HOST ?? 'localhost';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
host,
port: 5432,
user: username,
password,
database: databaseName ?? 'demo',
ssl: false,
}
});
migration 的部分我們建立在 src/db/migrate.ts
這時候我們把一些與資料庫相關的常見的指令寫到 package.json 的 scripts
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "bun run src/db/migrate.ts",
"db:studio": "drizzle-kit studio"
},
簡單介紹一下三個功能
drizzle-kit generate
這個 會根據 schema 產生 sql migration 的檔案
bun run src/db/migrate.ts
這個會執行 migration 實際到用到資料庫內
drizzle-kit studio
這會產生一個資料庫管理介面
我們這裡先跑第一個指令
bun run db:generate
這裡會看到結果

接下來我們要把資料表 migrate 到我們的資料庫內

我們這裡提供 github 連結 給大家參考 drizzle-orm 的 CRUD 免得有額外的流水帳
今天我們完成了:
今天篇幅可能,比較長,我就不多做結論了