Chapter 05

与 Drizzle 集成

libSQL 裸 SQL 很爽,但企业项目仍然要类型安全。Drizzle 与 libSQL 配合最好——schema 在 TS、迁移自动化、查询完全类型推断。

5.1 为什么选 Drizzle

Turso/libSQL 的 ORM 选择上 Drizzle 优势明显:

Prisma 也支持 libSQL,但需要 preview feature flag + 代码生成器;中小项目用 Drizzle 更顺手。

5.2 安装

pnpm add drizzle-orm @libsql/client
pnpm add -D drizzle-kit

5.3 定义 schema

// db/schema.ts
import { sqliteTable, integer, text, real, blob } from 'drizzle-orm/sqlite-core';
import { relations, sql } from 'drizzle-orm';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  createdAt: integer('created_at', { mode: 'timestamp' })
    .notNull()
    .default(sql`(unixepoch())`),
});

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  userId: integer('user_id').notNull().references(() => users.id),
  title: text('title').notNull(),
  body: text('body'),
  published: integer('published', { mode: 'boolean' }).default(false),
});

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.userId], references: [users.id] }),
}));

关键列类型

Drizzle 列SQLite 存储JS 类型
integer(), autoIncrementINTEGERnumber
integer(x, { mode: 'boolean' })INTEGER 0/1boolean
integer(x, { mode: 'timestamp' })INTEGER(秒)Date
text()TEXTstring
text({ mode: 'json' })TEXT(JSON)自定义类型
real()REALnumber
blob()BLOBUint8Array

5.4 drizzle.config.ts

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './db/schema.ts',
  out: './db/migrations',
  dialect: 'turso',           // 或 'sqlite'
  dbCredentials: {
    url: process.env.TURSO_DATABASE_URL!,
    authToken: process.env.TURSO_AUTH_TOKEN!,
  },
});

dialect: 'turso' 会让 drizzle-kit 用 libSQL HTTP 驱动;本地开发纯 SQLite 文件就用 'sqlite'

5.5 生成和应用迁移

pnpm drizzle-kit generate     # 对比 schema vs 库,生成 SQL 到 db/migrations/
pnpm drizzle-kit migrate      # 把迁移 apply 到远端

# 原型阶段可以跳过 migration 文件,直接 push
pnpm drizzle-kit push

push 适合开发期;生产应该走 generate + migrate——版本可审计。

5.6 client 初始化

// db/index.ts
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import * as schema from './schema';

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

export const db = drizzle(client, { schema });

5.7 基础 CRUD

import { db } from './db';
import { users, posts } from './db/schema';
import { eq, desc } from 'drizzle-orm';

// Insert,返回插入结果
const [alice] = await db.insert(users).values({
  email: 'alice@example.com',
  name: 'Alice',
}).returning();

// Select
const all = await db.select().from(users).orderBy(desc(users.createdAt));

// Update
await db.update(users).set({ name: 'Alicia' }).where(eq(users.id, alice.id));

// Delete
await db.delete(posts).where(eq(posts.userId, alice.id));

5.8 关系查询(Relational Queries)

// 取用户 + 他们的已发布博文 + 每篇博文的标题
const result = await db.query.users.findMany({
  with: {
    posts: {
      where: (posts, { eq }) => eq(posts.published, true),
      columns: { id: true, title: true },
      orderBy: (posts, { desc }) => desc(posts.id),
    },
  },
});

Drizzle 把它编译成一条 SQL,而不是 N+1 查询——libSQL 执行一次就返回嵌套结构。

5.9 部署 Cloudflare Workers

// src/index.ts
import { Hono } from 'hono';
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client/web';
import * as schema from './schema';

type Env = { Bindings: { TURSO_URL: string; TURSO_TOKEN: string } };
const app = new Hono<Env>();

app.get('/posts', async (c) => {
  const client = createClient({
    url: c.env.TURSO_URL, authToken: c.env.TURSO_TOKEN,
  });
  const db = drizzle(client, { schema });
  const posts = await db.query.posts.findMany({
    where: (p, { eq }) => eq(p.published, true),
    limit: 20,
  });
  return c.json(posts);
});

export default app;

wrangler.toml 放 TURSO_URL / TURSO_TOKEN(secret),部署 wrangler deploy

5.10 嵌入式副本 + Drizzle(Node)

const client = createClient({
  url: 'file:./data/app.db',
  syncUrl: process.env.TURSO_URL!,
  authToken: process.env.TURSO_TOKEN!,
  syncInterval: 30,
});
await client.sync();

export const db = drizzle(client, { schema });

Drizzle 层完全无感——你换 syncUrl 方式底层客户端即可,上层 query 代码不变。

5.11 类型推断

// 从 schema 直接推插入/查询类型
import { type InferInsertModel, type InferSelectModel } from 'drizzle-orm';

type NewUser = InferInsertModel<typeof users>;
type User = InferSelectModel<typeof users>;

小结

Drizzle + libSQL = TypeScript 优先的 Turso 访问层。schema 在 TS、drizzle-kit 管迁移、query 返回值自动推断。部署时 Edge 用 /web driver、Node 可选嵌入式副本,上层代码一模一样。下一章深入事务与一致性。