发布于 2026-01-06 0 阅读
0

Drizzle ORM 入门指南

Drizzle ORM 入门指南

在今天的文章中,我将向您介绍一些与创建和应用迁移、定义表模式以及如何使用Drizzle ORM 与数据库本身交互相关的信息。

介绍

我之前多次使用过 Drizzle,但从未专门写过一篇关于它的文章,作为新用户的指南。文章应涵盖不同的主题,并提供示例和文档链接。

将涵盖哪些内容

  • 使用 Drizzle Kit 配置迁移
  • 使用 Drizzle ORM 进行数据建模
  • 定义表之间的关系
  • 索引和约束的定义
  • 与数据库交互

先决条件

我们希望您具备Node.js的基础知识,并且过去使用过ORM或查询构建器,以及关系型数据库的基础知识。

入门

首先我们需要安装必要的依赖项:

npm install drizzle-orm better-sqlite3
Enter fullscreen mode Exit fullscreen mode

正如您在上一条命令中可能已经注意到的,在今天的文章中,我们将使用 SQLite 方言,以便尽可能多的人可以在不运行进程的情况下进行尝试。

此外,我们还需要为开发环境安装以下依赖项:

npm install --dev drizzle-kit @types/better-sqlite3
Enter fullscreen mode Exit fullscreen mode

安装好依赖项后,我们可以继续进行 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;
Enter fullscreen mode Exit fullscreen mode

默认文件名是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(),
});
Enter fullscreen mode Exit fullscreen mode

上面的代码中有一个名为 `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" }),
  });
Enter fullscreen mode Exit fullscreen mode

上面的代码片段中有一个名为 `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
    ),
  })
);
Enter fullscreen mode Exit fullscreen mode

需要说明的是,我们在外键的定义中已经明确规定,当用户被删除时,所有与该用户相关的行都必须被删除。

表关系

定义好两个表之后,我们需要指定它们之间的关系。我刚才提到过,这种关系是一对多的,我们可以这样定义:

// 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],
  }),
}));
Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,我们指定了表之间的关系,以及每个表的键应该映射到哪些列。

表格users可以包含多个用户tasks,但一个任务只能与一个用户关联。这样,表格中“用户”和“任务”列之间创建的约束得到tasks形式化。startenduser_id

迁徙

数据库表定义完毕,表之间的关系也已指定,现在我们可以创建第一个迁移了。为此,只需执行以下命令:

npm run drizzle-kit generate:sqlite
Enter fullscreen mode Exit fullscreen mode

上述命令会考虑drizzle.config.ts我们在文章开头创建的文件,--config=如果给文件赋予了其他名称,则必须在此命令中指定标志。

预期行为是,/migrations使用新创建的迁移文件创建一个名为“文件夹”的文件。

如果迁移成功,我们现在可以通过运行以下命令来应用创建的同一迁移:

npm run drizzle-kit push:sqlite
Enter fullscreen mode Exit fullscreen mode

预期行为是,终端将显示即将应用的迁移信息,并提示是否要应用这些更改。出于同样的原因,我们在 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,
});
Enter fullscreen mode Exit fullscreen mode

考虑到上面的代码片段,值得强调的一点是从数据库导入模式,这可以用于在文本编辑器中实现智能感知。您很快就会感受到这一点带来的好处。

通过该.insert() 方法,我们可以定义要向哪个表中添加新行,并通过该.values()方法定义要插入的数据。Object如果只想添加一行,则数据可以是整数;Array如果只想添加多行,则数据可以是多列。

// single row
await db.insert(users).values({ username: "Foo" });

// multiple rows
await db
  .insert(users)
  .values([
    { username: "Bar" },
    { username: "Baz" }
  ]);
Enter fullscreen mode Exit fullscreen mode

在上面的示例中,返回的Promise只是一些元数据,例如已更改的行等。如果您希望返回插入行的数据,可以使用该.returning()方法。

await db
  .insert(users)
  .values({ username: "Foo" })
  .returning();
Enter fullscreen mode Exit fullscreen mode

如果按照表格的模式users,插入名为 的用户时应该会发生错误Foo,因为该用户是在之前的示例中添加到此示例中的。

为此,.onConflictDoNothing()如果我们不希望在发生冲突时抛出错误,可以使用该方法,因为已经指定必须username是唯一的。

await db
  .insert(users)
  .values({ username: "Foo" })
  .onConflictDoNothing();
Enter fullscreen mode Exit fullscreen mode

如果要更新特定用户,我们可以使用指定要更新的表的.update() 方法.set()。同样,我们也应该利用该方法定义要更改的列和行.where()。方法如下:

await db
  .update(users)
  .set({ username: "Baz" })
  .where(eq(users.username, "Buzz Lightyear"))
  .returning();
Enter fullscreen mode Exit fullscreen mode

另一方面,如果我们想要删除一行,我们可以利用这种.delete() 方法,即必须指定要对哪个表执行此操作。

需要注意的是,如果.where()不使用该方法,则表中的所有行都将被删除。

// delete one row
await db.delete(users).where(eq(users.username, "Bar");

// clear table
await db.delete(users);
Enter fullscreen mode Exit fullscreen mode

要获取表格的所有行,可以通过以下方式:

// SQL-like way (most common)
await db.select().from(users);

// Drizzle query
await db.query.users.findMany();
Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,我们可以使用方法链来指定可以选择哪些列(在上面的示例中,是所有列)以及应该对哪个表执行此操作。第二种方法提供的体验与其他 ORM 非常相似。

从现在开始,我们将以查询示例为例,如果我们想要获取符合特定列要求的行,datum我们可以这样做:

await db.query.users.findFirst({
  where: (user, { eq }) => eq(user.username, "Bar"),
});
Enter fullscreen mode Exit fullscreen mode

另一个有趣的地方是,使用此 API 我们还可以datums从其他表中查询关系,例如获取表名user及其关联关系tasks。方法如下:

await db.query.users.findFirst({
  where: (user, { eq }) => eq(user.username, "Bar"),
  with: {
    tasks: true,
  }
});
Enter fullscreen mode Exit fullscreen mode

如果我们需要更细粒度的控制,我们可以选择查询中要返回的users表和表的哪些列,如下所示:tasks

await db.query.users.findFirst({
  where: (user, { eq }) => eq(user.username, "Bar"),
  columns: {
    username: true
  },
  with: {
    tasks: {
      columns: {
        id: true,
        name: true
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

在前面的例子中,users我们从表中选择了username列,而从tasks表中我们选择了idname列。

本文到此结束。我的目的是概述一些我在其他使用 Drizzle 的文章中没有提及但我觉得有必要介绍的内容,至少希望能对您使用 Drizzle 的最初二三十分钟有所帮助。

结论

无论你是将这些信息运用到现有项目中,还是仅仅出于兴趣尝试一下,我都希望这篇文章对你有所帮助。

如果您在文章中发现任何错误,请留言告知。

文章来源:https://dev.to/franciscomendes10866/getting-started-with-drizzle-orm-a-beginners-tutorial-4782