在 Turso 数据库中使用 Drizzle ORM

本教程演示了如何使用 Turso 与 Drizzle ORM。

This guide assumes familiarity with:
  • 您应该已经安装了 Drizzle ORM 和 Drizzle kit。您可以通过运行以下命令来安装:
npm
yarn
pnpm
bun
npm i drizzle-orm
npm i -D drizzle-kit
  • 您应该已经安装了 dotenv 包以管理环境变量。您可以在这里了解更多关于此包的信息。
npm
yarn
pnpm
bun
npm i dotenv
  • 您应该已经安装了 @libsql/client 包。请在这里了解更多关于此包的信息。
npm
yarn
pnpm
bun
npm i @libsql/client
  • 您应该已经安装了 Turso CLI。请查阅文档以了解更多信息。

Turso 是一个兼容 SQLite 的数据库,构建在 libSQL 上,libSQL 是 SQLite 的开放提交分支。它支持每个组织的数十万个数据库的扩展,并支持复制到任何位置,包括您自己的服务器,以实现微秒级访问延迟。您可以在这里了解更多有关 Turso 概念的信息。

Drizzle ORM 本地支持 libSQL 驱动程序,我们支持 SQL 方言和特定于方言的驱动程序和语法,并且镜像了最流行的 SQLite-like 的 allgetvaluesrun 查询方法的语法。

请参阅官方文档以设置 Turso 数据库。

设置 Turso 和 Drizzle ORM

注册或登录 Turso

注册:

turso auth signup

登录:

turso auth login

创建新数据库

通过运行 turso db create <DATABASE_NAME> 命令创建一个新数据库:

turso db create drizzle-turso-db

要查看有关数据库的信息,请运行以下命令:

turso db show drizzle-turso-db

创建身份验证令牌

要为您的数据库创建身份验证令牌,请运行以下命令:

turso db tokens create drizzle-turso-db

文档中了解有关此命令及其选项的更多信息。

更新环境变量

使用数据库连接 URL 和身份验证令牌更新您的 .env.env.local 文件。

TURSO_CONNECTION_URL=
TURSO_AUTH_TOKEN=

连接 Drizzle ORM 到您的数据库

src/db 目录中创建一个 index.ts 文件,并设置您的数据库配置:

src/db/index.ts
import { config } from 'dotenv';
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';

config({ path: '.env' }); // or .env.local

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

export const db = drizzle(client);

创建表

src/db 目录下创建一个 schema.ts 文件,并声明你的表:

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

export const usersTable = sqliteTable('users', {
  id: integer('id').primaryKey(),
  name: text('name').notNull(),
  age: integer('age').notNull(),
  email: text('email').unique().notNull(),
});

export const postsTable = sqliteTable('posts', {
  id: integer('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content').notNull(),
  userId: integer('user_id')
    .notNull()
    .references(() => usersTable.id, { onDelete: 'cascade' }),
  createdAt: text('created_at')
    .default(sql`(CURRENT_TIMESTAMP)`)
    .notNull(),
  updateAt: integer('updated_at', { mode: 'timestamp' }).$onUpdate(() => new Date()),
});

export type InsertUser = typeof usersTable.$inferInsert;
export type SelectUser = typeof usersTable.$inferSelect;

export type InsertPost = typeof postsTable.$inferInsert;
export type SelectPost = typeof postsTable.$inferSelect;

设置 Drizzle 配置文件

Drizzle 配置 - 此配置文件用于 Drizzle Kit,其中包含有关您的数据库连接、迁移文件夹和模式文件的所有信息。

在项目根目录中创建一个 drizzle.config.ts 文件,并添加以下内容:

drizzle.config.ts
import { config } from 'dotenv';
import { defineConfig } from 'drizzle-kit';

config({ path: '.env' });

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

将更改应用到数据库

您可以使用 drizzle-kit generate 命令生成迁移,然后使用 drizzle-kit migrate 命令运行它们。

生成迁移:

npx drizzle-kit generate

这些迁移保存在 migrations 目录中,如您在 drizzle.config.ts 中指定的那样。此目录将包含更新数据库模式所需的 SQL 文件以及用于在不同迁移阶段存储模式快照的 meta 文件夹。

生成的迁移示例:

CREATE TABLE `posts` (
  `id` integer PRIMARY KEY NOT NULL,
  `title` text NOT NULL,
  `content` text NOT NULL,
  `user_id` integer NOT NULL,
  `created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
  `updated_at` integer,
  FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `users` (
  `id` integer PRIMARY KEY NOT NULL,
  `name` text NOT NULL,
  `age` integer NOT NULL,
  `email` text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);

运行迁移:

npx drizzle-kit migrate

或者,您可以直接使用 Drizzle Kit 推送命令将更改推送到数据库:

npx drizzle-kit push
💡
推送命令适用于需要在本地开发环境中快速测试新的模式设计或更改的情况,它允许快速迭代而无需管理迁移文件的开销。

基本文件结构

这是项目的基本文件结构。在 src/db 目录中,我们有与数据库相关的文件,包括 index.ts 中的连接和 schema.ts 中的模式定义。

📦 <project root>
 ├ 📂 src
 │   ├ 📂 db
 │   │  ├ 📜 index.ts
 │   │  └ 📜 schema.ts
 ├ 📂 migrations
 │  ├ 📂 meta
 │  │  ├ 📜 _journal.json
 │  │  └ 📜 0000_snapshot.json
 │  └ 📜 0000_watery_spencer_smythe.sql
 ├ 📜 .env
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

查询示例

例如,我们创建 src/db/queries 文件夹,并针对每个操作创建单独的文件:insert、select、update、delete。

插入数据

文档中阅读有关插入查询的更多信息。

src/db/queries/insert.ts
import { db } from '../index';
import { InsertPost, InsertUser, postsTable, usersTable } from '../schema';

export async function createUser(data: InsertUser) {
  await db.insert(usersTable).values(data);
}

export async function createPost(data: InsertPost) {
  await db.insert(postsTable).values(data);
}

查询数据

文档中阅读有关选择查询的更多信息。

src/db/queries/select.ts
import { asc, count, eq, getTableColumns, gt, sql } from 'drizzle-orm';
import { db } from '../index';
import { SelectUser, postsTable, usersTable } from '../schema';

export async function getUserById(id: SelectUser['id']): Promise<
  Array<{
    id: number;
    name: string;
    age: number;
    email: string;
  }>
> {
  return db.select().from(usersTable).where(eq(usersTable.id, id));
}

export async function getUsersWithPostsCount(
  page = 1,
  pageSize = 5,
): Promise<
  Array<{
    postsCount: number;
    id: number;
    name: string;
    age: number;
    email: string;
  }>
> {
  return db
    .select({
      ...getTableColumns(usersTable),
      postsCount: count(postsTable.id),
    })
    .from(usersTable)
    .leftJoin(postsTable, eq(usersTable.id, postsTable.userId))
    .groupBy(usersTable.id)
    .orderBy(asc(usersTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

export async function getPostsForLast24Hours(
  page = 1,
  pageSize = 5,
): Promise<
  Array<{
    id: number;
    title: string;
  }>
> {
  return db
    .select({
      id: postsTable.id,
      title: postsTable.title,
    })
    .from(postsTable)
    .where(gt(postsTable.createdAt, sql`(datetime('now','-24 hour'))`))
    .orderBy(asc(postsTable.title), asc(postsTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

或者,您可以使用关系查询语法

更新数据

文档中阅读有关更新查询的更多信息。

src/db/queries/update.ts
import { eq } from 'drizzle-orm';
import { db } from '../index';
import { SelectPost, postsTable } from '../schema';

export async function updatePost(id: SelectPost['id'], data: Partial<Omit<SelectPost, 'id'>>) {
  await db.update(postsTable).set(data).where(eq(postsTable.id, id));
}

删除数据

文档中阅读有关删除查询的更多信息。

src/db/queries/delete.ts
import { eq } from 'drizzle-orm';
import { db } from '../index';
import { SelectUser, usersTable } from '../schema';

export async function deleteUser(id: SelectUser['id']) {
  await db.delete(usersTable).where(eq(usersTable.id, id));
}
Become a Gold Sponsor