iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

day18_title

前言

在前面的章節,我們除了一開始的基礎操作,隨後到一些 bun 特別的使用
今天開始我們要用 bun 開始做些專案,以及實戰,去展現出bun 本身的全端能力
當然建立全端前,我們先搞定後端的部分,我們會用 DrizzleORM 去完成一個簡單的後端系統
並且整合知名且這年頭最有名的資料庫 postgresSQL,配合 ORM 完成我們簡單的資料庫串接與
CRUD

為什麼選擇 DrizzleORM

仿間有非常多 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

簡短比較結論

  • Drizzle ORM:想要「像寫 SQL 一樣寫 TS」、極致效能與 Edge 相容性 → 選它
  • TypeORM:熟悉 OOP/Java Hibernate 風格、搭配 NestJS 開發企業應用 → 選它
  • Prisma:重視開發體驗(DX)、型別安全、且能接受多一層 Engine 開銷 → 選它

那本身是老牌工程師的各位,對於寫 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

開始撰寫專案

database schema

我們先建立資料表的 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-kit

我們建立一個 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 的語法

migration 的部分我們建立在 src/db/migrate.ts


package.json script

這時候我們把一些與資料庫相關的常見的指令寫到 package.jsonscripts

  "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

這裡會看到結果

database_generate

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

database_sample

CRUD

我們這裡提供 github 連結 給大家參考 drizzle-orm 的 CRUD 免得有額外的流水帳

可以 clone 下來玩玩看

結論

今天我們完成了:

  1. 用 Docker 快速起一個 PostgreSQL 實例
  2. 用 Drizzle ORM 定義 schema、產生並執行 migration
  3. 用 Bun 原生的 Bun.serve 搭配 Drizzle 完成一套完整的 CRUD

今天篇幅可能,比較長,我就不多做結論了


上一篇
Elysia + Eden:端到端型別安全,不再手刻 API 型別
下一篇
認證系統實作:JWT + Session 在 Bun 中的最佳實踐(上篇)
系列文
不只是快 —— Bun 30 天:從底層架構、全套工具鏈到生產部署19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言