Drizzle ORM 入门指南
在今天的文章中,我将向您介绍一些与创建和应用迁移、定义表模式以及如何使用Drizzle ORM 与数据库本身交互相关的信息。
介绍
我之前多次使用过 Drizzle,但从未专门写过一篇关于它的文章,作为新用户的指南。文章应涵盖不同的主题,并提供示例和文档链接。
将涵盖哪些内容
- 使用 Drizzle Kit 配置迁移
- 使用 Drizzle ORM 进行数据建模
- 定义表之间的关系
- 索引和约束的定义
- 与数据库交互
先决条件
我们希望您具备Node.js的基础知识,并且过去使用过ORM或查询构建器,以及关系型数据库的基础知识。
入门
首先我们需要安装必要的依赖项:
npm install drizzle-orm better-sqlite3
正如您在上一条命令中可能已经注意到的,在今天的文章中,我们将使用 SQLite 方言,以便尽可能多的人可以在不运行进程的情况下进行尝试。
此外,我们还需要为开发环境安装以下依赖项:
npm install --dev drizzle-kit @types/better-sqlite3
安装好依赖项后,我们可以继续进行 drizzle配置,以便定义数据库模式的路径以及迁移文件应该在哪个路径中生成。
// drizzle.config.ts
import type { Config } from "drizzle-kit";
export default {
schema: "./schema.ts",
out: "./migrations",
driver: "better-sqlite",
dbCredentials: {
url: "./local.db",
},
verbose: true,
strict: true,
} satisfies Config;
默认文件名是drizzle.config.ts,值得一提的是,文件名可能不同,但是运行drizzle-kit--config=时,必须在文件路径配置中指定标志。
说到配置文件,其中定义了数据库模式路径、迁移路径、要使用的驱动程序以及 SQLite 数据库路径。最后但同样重要的是,如果您希望在应用迁移时获得更多信息的提示,则 ` verboseand`属性非常有用。strict
表结构
接下来,我们可以进入下一个要点,即数据库表模式的定义。Drizzle 包含一系列特定于每种方言的基本类型。以下表就是一个例子:
// schema.ts
import {
sqliteTable,
integer,
text,
} from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: integer("id").primaryKey({ autoIncrement: true }),
username: text("username").unique().notNull(),
});
上面的代码中有一个名为 `table` 的表,users其中包含两列。id第一列的数据类型为整数,它是一个自增主键。username与数据类型为文本的第二列一样,它必须唯一且不能为空。
这次我们将创建第二个表,该表必须包含一对多关系。请参考以下表格:
// schema.ts
import {
unique,
sqliteTable,
integer,
text,
} from "drizzle-orm/sqlite-core";
// ...
export const tasks = sqliteTable(
"tasks",
{
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
start: integer("start", { mode: "timestamp" }).notNull(),
end: integer("end", { mode: "timestamp" }).notNull(),
userId: integer("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
});
上面的代码片段中有一个名为 `table` 的表tasks,它有以下五列:
id这是主键name数据类型为文本,且不能为空start并且end,这两列都是时间戳,不能为空。user_id这是引用一个外键的user
现在让我们考虑以下使用场景:
“我们经常会根据 `
startand`end列进行查询,就像这些列必须根据 `is` 列的唯一性来保证其唯一性一样user_id。”
考虑到上一段所述情况,理想的做法是定义 ` startand`end列需要建立索引,并在这些列和 `and` 列之间创建约束,user_id以确保它们的唯一性。例如:
// schema.ts
import {
unique,
sqliteTable,
integer,
text,
} from "drizzle-orm/sqlite-core";
// ...
export const tasks = sqliteTable(
"tasks",
{
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
start: integer("start", { mode: "timestamp" }).notNull(),
end: integer("end", { mode: "timestamp" }).notNull(),
userId: integer("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
},
(table) => ({
startIndex: index("start_index").on(table.start),
endIndex: index("end_index").on(table.end),
timeUniqueConstraint: unique("time_unique_constraint").on(
table.start,
table.end,
table.userId
),
})
);
需要说明的是,我们在外键的定义中已经明确规定,当用户被删除时,所有与该用户相关的行都必须被删除。
表关系
定义好两个表之后,我们需要指定它们之间的关系。我刚才提到过,这种关系是一对多的,我们可以这样定义:
// schema.ts
import { relations } from "drizzle-orm";
// ...
export const userRelations = relations(users, ({ many }) => ({
tasks: many(tasks),
}));
export const tasksRelations = relations(tasks, ({ one }) => ({
user: one(users, {
fields: [tasks.userId],
references: [users.id],
}),
}));
在上面的代码片段中,我们指定了表之间的关系,以及每个表的键应该映射到哪些列。
表格users可以包含多个用户tasks,但一个任务只能与一个用户关联。这样,表格中“用户”和“任务”列之间创建的约束也得到tasks了形式化。startenduser_id
迁徙
数据库表定义完毕,表之间的关系也已指定,现在我们可以创建第一个迁移了。为此,只需执行以下命令:
npm run drizzle-kit generate:sqlite
上述命令会考虑drizzle.config.ts我们在文章开头创建的文件,--config=如果给文件赋予了其他名称,则必须在此命令中指定标志。
预期行为是,/migrations使用新创建的迁移文件创建一个名为“文件夹”的文件。
如果迁移成功,我们现在可以通过运行以下命令来应用创建的同一迁移:
npm run drizzle-kit push:sqlite
预期行为是,终端将显示即将应用的迁移信息,并提示是否要应用这些更改。出于同样的原因,我们在 Drizzle 配置文件中添加了` verboseand`属性。strict
数据库客户端
将迁移文件导入数据库后,我们可以进行下一步,即创建数据库客户端。其代码可能类似于以下示例:
// db.ts
import {
drizzle,
type BetterSQLite3Database,
} from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import * as schema from "./schema";
const sqlite = new Database("local.db");
export const db: BetterSQLite3Database<typeof schema> = drizzle(sqlite, {
schema,
});
考虑到上面的代码片段,值得强调的一点是从数据库导入模式,这可以用于在文本编辑器中实现智能感知。您很快就会感受到这一点带来的好处。
通过该.insert() 方法,我们可以定义要向哪个表中添加新行,并通过该.values()方法定义要插入的数据。Object如果只想添加一行,则数据可以是整数;Array如果只想添加多行,则数据可以是多列。
// single row
await db.insert(users).values({ username: "Foo" });
// multiple rows
await db
.insert(users)
.values([
{ username: "Bar" },
{ username: "Baz" }
]);
在上面的示例中,返回的Promise只是一些元数据,例如已更改的行等。如果您希望返回插入行的数据,可以使用该.returning()方法。
await db
.insert(users)
.values({ username: "Foo" })
.returning();
如果按照表格的模式users,插入名为 的用户时应该会发生错误Foo,因为该用户是在之前的示例中添加到此示例中的。
为此,.onConflictDoNothing()如果我们不希望在发生冲突时抛出错误,可以使用该方法,因为已经指定必须username是唯一的。
await db
.insert(users)
.values({ username: "Foo" })
.onConflictDoNothing();
如果要更新特定用户,我们可以使用指定要更新的表的.update() 方法.set()。同样,我们也应该利用该方法定义要更改的列和行.where()。方法如下:
await db
.update(users)
.set({ username: "Baz" })
.where(eq(users.username, "Buzz Lightyear"))
.returning();
另一方面,如果我们想要删除一行,我们可以利用这种.delete() 方法,即必须指定要对哪个表执行此操作。
需要注意的是,如果.where()不使用该方法,则表中的所有行都将被删除。
// delete one row
await db.delete(users).where(eq(users.username, "Bar");
// clear table
await db.delete(users);
要获取表格的所有行,可以通过以下方式:
// SQL-like way (most common)
await db.select().from(users);
// Drizzle query
await db.query.users.findMany();
在上面的代码片段中,我们可以使用方法链来指定可以选择哪些列(在上面的示例中,是所有列)以及应该对哪个表执行此操作。第二种方法提供的体验与其他 ORM 非常相似。
从现在开始,我们将以查询示例为例,如果我们想要获取符合特定列要求的行,datum我们可以这样做:
await db.query.users.findFirst({
where: (user, { eq }) => eq(user.username, "Bar"),
});
另一个有趣的地方是,使用此 API 我们还可以datums从其他表中查询关系,例如获取表名user及其关联关系tasks。方法如下:
await db.query.users.findFirst({
where: (user, { eq }) => eq(user.username, "Bar"),
with: {
tasks: true,
}
});
如果我们需要更细粒度的控制,我们可以选择查询中要返回的users表和表的哪些列,如下所示:tasks
await db.query.users.findFirst({
where: (user, { eq }) => eq(user.username, "Bar"),
columns: {
username: true
},
with: {
tasks: {
columns: {
id: true,
name: true
}
}
}
});
在前面的例子中,users我们从表中选择了username列,而从tasks表中我们选择了id和name列。
本文到此结束。我的目的是概述一些我在其他使用 Drizzle 的文章中没有提及但我觉得有必要介绍的内容,至少希望能对您使用 Drizzle 的最初二三十分钟有所帮助。
结论
无论你是将这些信息运用到现有项目中,还是仅仅出于兴趣尝试一下,我都希望这篇文章对你有所帮助。
如果您在文章中发现任何错误,请留言告知。
文章来源:https://dev.to/franciscomendes10866/getting-started-with-drizzle-orm-a-beginners-tutorial-4782