Chapter 02

Schema:TypeScript 定义表

把整个数据库结构写成 TypeScript 源码——表、列、默认值、索引、外键——然后让 IDE 给你类型补全。

2.1 基本结构

import { pgTable, serial, text, integer, timestamp, boolean } from "drizzle-orm/pg-core";

export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  email: text("email").notNull().unique(),
  name: text("name").notNull(),
  age: integer("age"),
  isActive: boolean("is_active").default(true),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

第一个参数是数据库中的表名,第二个参数是列的字典——key 是 TS 里的属性名,value 是列定义。列定义的第一个参数数据库中的列名

命名约定

TS 里用 camelCase(createdAt),数据库用 snake_case(created_at)——这是 Drizzle 社区的惯例,SQL 里更可读,TS 代码也更自然。

2.2 三大方言的 core 包

import { pgTable }    from "drizzle-orm/pg-core";     // Postgres
import { mysqlTable } from "drizzle-orm/mysql-core";  // MySQL
import { sqliteTable } from "drizzle-orm/sqlite-core"; // SQLite

每个方言有自己的列类型(Postgres 有 uuid、jsonb、tsvector;MySQL 有 varchar/decimal;SQLite 类型系统最简单)。本章主要按 Postgres 举例。

2.3 常用列类型(Postgres)

TS 函数Postgres 类型TS 读取类型
serialSERIAL(自增 int)number
bigserialBIGSERIALbigint
integer / smallint / bigintINT / SMALLINT / BIGINTnumber(bigint 是 string,除非开 mode)
real / doublePrecisionREAL / DOUBLE PRECISIONnumber
numeric / decimalNUMERIC(p, s)string(精度不丢)
textTEXTstring
varcharVARCHAR(n)string
charCHAR(n)string
booleanBOOLEANboolean
timestampTIMESTAMPDate
timestamp({ withTimezone: true })TIMESTAMPTZDate
date / timeDATE / TIMEstring
intervalINTERVALstring
uuidUUIDstring
json / jsonbJSON / JSONBunknown(或泛型指定)
pgEnum自定义枚举字面联合
byteaBYTEAUint8Array

2.4 默认值

createdAt: timestamp("created_at").defaultNow(),           // NOW()
isActive:  boolean("is_active").default(true),               // 字面值
uuid:      uuid("uuid").defaultRandom(),                     // gen_random_uuid()
config:    jsonb("config").$type<{ theme: string }>().default({ theme: "dark" }),

// 任意 SQL 表达式
slug: text("slug").default(sql`gen_random_uuid()::text`),

2.5 枚举(Postgres)

import { pgEnum, pgTable } from "drizzle-orm/pg-core";

export const roleEnum = pgEnum("role", ["admin", "editor", "viewer"]);

export const users = pgTable("users", {
  id: serial().primaryKey(),
  role: roleEnum("role").notNull().default("viewer"),
});

// role 列的类型被推断为 "admin" | "editor" | "viewer"

2.6 主键与联合主键

// 单列主键(在列上)
id: serial("id").primaryKey(),

// 联合主键(在表的第三参里)
import { primaryKey } from "drizzle-orm/pg-core";

export const userRoles = pgTable("user_roles", {
  userId: integer("user_id").notNull(),
  roleId: integer("role_id").notNull(),
}, (t) => ({
  pk: primaryKey({ columns: [t.userId, t.roleId] }),
}));

2.7 唯一与 unique 约束

// 列级 unique
email: text("email").unique(),

// 复合 unique
import { unique } from "drizzle-orm/pg-core";

export const follows = pgTable("follows", {
  followerId: integer("follower_id").notNull(),
  followeeId: integer("followee_id").notNull(),
}, (t) => ({
  uniq: unique().on(t.followerId, t.followeeId),
}));

2.8 索引

import { index, uniqueIndex } from "drizzle-orm/pg-core";

export const posts = pgTable("posts", {
  id: serial().primaryKey(),
  title: text().notNull(),
  authorId: integer("author_id").notNull(),
  publishedAt: timestamp("published_at"),
}, (t) => ({
  // 普通索引
  authorIdx: index("posts_author_idx").on(t.authorId),
  // 复合索引
  authorPubIdx: index().on(t.authorId, t.publishedAt.desc()),
  // 唯一索引
  titleUniq: uniqueIndex().on(t.title),
  // 部分索引(Postgres)
  published: index().on(t.publishedAt).where(sql`published_at IS NOT NULL`),
}));

2.9 外键

export const posts = pgTable("posts", {
  id: serial().primaryKey(),
  title: text().notNull(),

  // 引用 users.id,删除作者时级联删文章
  authorId: integer("author_id")
    .notNull()
    .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }),
});

onDelete/onUpdate 取值:"cascade" / "restrict" / "no action" / "set null" / "set default"

2.10 JSON / JSONB 列的类型

type Settings = { theme: "dark" | "light"; lang: string };

export const users = pgTable("users", {
  id: serial().primaryKey(),
  settings: jsonb("settings").$type<Settings>().default({ theme: "dark", lang: "zh" }),
});

// 现在 user.settings.theme 有自动补全

2.11 "Schema 瘦身":用泛型扩展

多表共用某组列(id/createdAt/updatedAt)时提炼函数:

const timestamps = {
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
};

export const users = pgTable("users", {
  id: serial().primaryKey(),
  name: text().notNull(),
  ...timestamps,
});

2.12 推断 InsertType 与 SelectType

type User        = typeof users.$inferSelect;   // 读:含 id/createdAt
type NewUser     = typeof users.$inferInsert;   // 写:可省 id/createdAt

这两个类型跟着 schema 自动变,不用自己维护 DTO。

2.13 小结