在 Neon Postgres 中使用 Drizzle ORM

本教程演示了如何使用 Drizzle ORM 与 Neon Postgres 数据库。如果您还没有现有的 Neon 账户,请在这里注册。

This guide assumes familiarity with:
  • 您应该已经安装了 Drizzle ORM 和 Drizzle kit。您可以通过运行以下命令来安装它们:
npm
yarn
pnpm
bun
npm i drizzle-orm
npm i -D drizzle-kit
npm
yarn
pnpm
bun
npm i @neondatabase/serverless
  • 您应该已经安装了 dotenv 包来管理环境变量。
npm
yarn
pnpm
bun
npm i dotenv

设置 Neon 和 Drizzle ORM

创建一个新的 Neon 项目

登录到 Neon 控制台,导航到项目部分。选择一个项目或点击 “New Project” 按钮来创建一个新的项目。

您的 Neon 项目有一个名为 neondb 的预配置 Postgres 数据库。我们将在本教程中使用它。

Setup connection string variable

在项目控制台的 Connection Details 部分查找您的数据库连接字符串。它应该类似于:

postgres://username:password@ep-cool-darkness-123456.us-east-2.aws.neon.tech/neondb

DATABASE_URL 环境变量添加到您的 .env.env.local 文件中,用来连接到 Neon 数据库。

DATABASE_URL=NEON_DATABASE_CONNECTION_STRING

连接 Drizzle ORM 到你的数据库

创建一个 db.ts 文件并设置数据库配置:

src/db.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import { config } from "dotenv";

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

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);

创建表格

创建一个 schema.ts 文件并声明你的表格:

src/schema.ts
import { integer, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';

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

export const postsTable = pgTable('posts_table', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content').notNull(),
  userId: integer('user_id')
    .notNull()
    .references(() => usersTable.id, { onDelete: 'cascade' }),
  createdAt: timestamp('created_at').notNull().defaultNow(),
  updatedAt: timestamp('updated_at')
    .notNull()
    .$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/schema.ts",
  out: "./migrations",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Applying changes to the database

You can generate migrations using drizzle-kit generate command and then run them using the drizzle-kit migrate command.

Generate migrations:

npx drizzle-kit generate

These migrations are stored in the drizzle/migrations directory, as specified in your drizzle.config.ts. This directory will contain the SQL files necessary to update your database schema and a meta folder for storing snapshots of the schema at different migration stages.

Example of a generated migration:

CREATE TABLE IF NOT EXISTS "posts_table" (
  "id" serial PRIMARY KEY NOT NULL,
  "title" text NOT NULL,
  "content" text NOT NULL,
  "user_id" integer NOT NULL,
  "created_at" timestamp DEFAULT now() NOT NULL,
  "updated_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "users_table" (
  "id" serial PRIMARY KEY NOT NULL,
  "name" text NOT NULL,
  "age" integer NOT NULL,
  "email" text NOT NULL,
  CONSTRAINT "users_table_email_unique" UNIQUE("email")
);
--> statement-breakpoint
DO $$ BEGIN
 ALTER TABLE "posts_table" ADD CONSTRAINT "posts_table_user_id_users_table_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users_table"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
 WHEN duplicate_object THEN null;
END $$;

Run migrations:

npx drizzle-kit migrate

Alternatively, you can push changes directly to the database using Drizzle kit push command:

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

基本文件结构

这是项目的基本文件结构。在 src/db 目录中,我们有与数据库相关的文件,包括连接在 db.ts 中,模式定义在 schema.ts 中,以及存储在 migrations 目录中的迁移脚本。

📦 <项目根目录>
 ├ 📂 src
 │  ├ 📜 db.ts
 │  └ 📜 schema.ts
 ├ 📂 migrations
 │  ├ 📂 meta
 │  │  ├ 📜 _journal.json
 │  │  └ 📜 0000_snapshot.json
 │  └ 📜 0000_dry_richard_fisk.sql
 ├ 📜 .env
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

查询示例

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

插入数据

文档中了解更多关于插入查询的信息。

src/queries/insert.ts
import { db } from '../db';
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/queries/select.ts
import { asc, between, count, eq, getTableColumns, sql } from 'drizzle-orm';
import { db } from '../db';
import { SelectUser, usersTable, postsTable } 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(between(postsTable.createdAt, sql`now() - interval '1 day'`, sql`now()`))
    .orderBy(asc(postsTable.title), asc(postsTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

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

更新数据

文档中了解更多关于更新查询的信息。

src/queries/update.ts
import { eq } from 'drizzle-orm';
import { db } from '../db';
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/queries/delete.ts
import { db } from '../db';
import { eq } from 'drizzle-orm';
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