功能特性数据库

数据库

ShipAny 使用 drizzle-orm 支持多种类型数据库。

配置数据库

以 Supabase 数据库为例,在 ShipAny 配置数据库的流程为:

  1. 创建数据库

登录 Supabase 控制台,创建数据库

  1. 查看数据库连接信息

在 Supabase 控制台,进入你创建的数据库,点击上方的 Connect

在弹出来的框中复制数据库连接信息,类似这样的字符串:

postgresql://postgres.defqvdpquwyqqjlmurkg:[YOUR-PASSWORD]@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres

[YOUR-PASSWORD] 需要替换成你在创建数据库时设置的密码。

  1. 配置数据库

修改项目配置文件:.env.development.env.production

设置数据库连接信息:

DATABASE_URL="postgresql://postgres.defqvdpquwyqqjlmurkg:******@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres"

初始化数据库

在配置好 DATABASE_URL 后,在项目根目录下运行以下命令,初始化数据库:

pnpm db:migrate

这个命令会执行 src/db/migrations 目录下的所有迁移文件,创建数据库表。

如果你通过 DATABASE_URL 配置的不是一个新创建的数据库,或者数据库之前已经存在 ShipAny 的库表信息,请不要执行以上命令。而是比较 src/db/migrations 目录下所有迁移文件的 sql 内容,手动执行 sql 语句,更新数据库表。

更新数据库

如果在项目创建之处,你使用的是一个新的数据库,且是通过 pnpm db:migrate 命令初始化的数据库,如果后续你拉取了最新的代码,可以继续执行以下命令,更新数据库表:

pnpm db:migrate

这个命令会根据 src/db/migrations 目录下所有迁移文件的 sql 内容,增量更新数据库表。

管理数据库

在项目根目录下执行命令:pnpm db:studio

这个命令会打开一个数据库管理界面,你可以在这里查看、编辑、删除数据库表。

操作数据库

src/models 目录下,写数据库操作文件,实现对数据表的增删改查。可参考以下操作 posts 表的示例:

数据表操作语法可参考 drizzle-orm 文档。

import { posts } from "@/db/schema";
import { db } from "@/db";
import { and, desc, eq } from "drizzle-orm";
 
export enum PostStatus {
  Created = "created",
  Deleted = "deleted",
  Online = "online",
  Offline = "offline",
}
 
export async function insertPost(
  data: typeof posts.$inferInsert
): Promise<typeof posts.$inferSelect | undefined> {
  const [post] = await db().insert(posts).values(data).returning();
 
  return post;
}
 
export async function updatePost(
  uuid: string,
  data: Partial<typeof posts.$inferInsert>
): Promise<typeof posts.$inferSelect | undefined> {
  const [post] = await db()
    .update(posts)
    .set(data)
    .where(eq(posts.uuid, uuid))
    .returning();
 
  return post;
}
 
export async function findPostByUuid(
  uuid: string
): Promise<typeof posts.$inferSelect | undefined> {
  const [post] = await db()
    .select()
    .from(posts)
    .where(eq(posts.uuid, uuid))
    .limit(1);
 
  return post;
}
 
export async function findPostBySlug(
  slug: string,
  locale: string
): Promise<typeof posts.$inferSelect | undefined> {
  const [post] = await db()
    .select()
    .from(posts)
    .where(and(eq(posts.slug, slug), eq(posts.locale, locale)))
    .limit(1);
 
  return post;
}
 
export async function getAllPosts(
  page: number = 1,
  limit: number = 50
): Promise<(typeof posts.$inferSelect)[] | undefined> {
  const offset = (page - 1) * limit;
 
  const data = await db()
    .select()
    .from(posts)
    .orderBy(desc(posts.created_at))
    .limit(limit)
    .offset(offset);
 
  return data;
}
 
export async function getPostsByLocale(
  locale: string,
  page: number = 1,
  limit: number = 50
): Promise<(typeof posts.$inferSelect)[] | undefined> {
  const offset = (page - 1) * limit;
 
  const data = await db()
    .select()
    .from(posts)
    .where(and(eq(posts.locale, locale), eq(posts.status, PostStatus.Online)))
    .orderBy(desc(posts.created_at))
    .limit(limit)
    .offset(offset);
 
  return data;
}
 
export async function getPostsTotal(): Promise<number> {
  const total = await db().$count(posts);
 
  return total;
}

使用其他类型数据库

如果你使用自建的 Postgres 数据库,或者使用其他兼容 postgres 的云数据库,比如:Neon。配置和连接数据库的步骤跟使用 Supabase 一致,你只需要填写数据库的 DATABASE_URL 即可。

如果你需要使用 MySQL 或者 SQLite 等数据库,可参考以下步骤自定义:

  1. 使用新的数据库 Schema

修改 src/db/schema.ts 文件,使用新的数据库 Schema。

Schema 中数据表的字段定义,可以参考 drizzle-orm 文档。

  1. 修改数据库配置

修改 src/db/config.ts 文件,使用新的数据库连接配置。

默认是 Postgres 数据库连接配置,你可以参考 drizzle-orm 文档按需修改。

import "dotenv/config";
import { config } from "dotenv";
import { defineConfig } from "drizzle-kit";
 
config({ path: ".env" });
config({ path: ".env.development" });
config({ path: ".env.local" });
 
export default defineConfig({
  out: "./src/db/migrations",
  schema: "./src/db/schema.ts",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});
  1. 修改数据库连接实例

根据你使用的数据库类型,修改数据库连接实例。

import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
 
// Detect if running in Cloudflare Workers environment
const isCloudflareWorker =
  typeof globalThis !== "undefined" && "Cloudflare" in globalThis;
 
// Database instance for Node.js environment
let dbInstance: ReturnType<typeof drizzle> | null = null;
 
export function db() {
  const databaseUrl = process.env.DATABASE_URL;
  if (!databaseUrl) {
    throw new Error("DATABASE_URL is not set");
  }
 
  // In Cloudflare Workers, create new connection each time
  if (isCloudflareWorker) {
    // Workers environment uses minimal configuration
    const client = postgres(databaseUrl, {
      prepare: false,
      max: 1, // Limit to 1 connection in Workers
      idle_timeout: 10, // Shorter timeout for Workers
      connect_timeout: 5,
    });
 
    return drizzle(client);
  }
 
  // In Node.js environment, use singleton pattern
  if (dbInstance) {
    return dbInstance;
  }
 
  // Node.js environment with connection pool configuration
  const client = postgres(databaseUrl, {
    prepare: false,
    max: 10, // Maximum connections in pool
    idle_timeout: 30, // Idle connection timeout (seconds)
    connect_timeout: 10, // Connection timeout (seconds)
  });
  dbInstance = drizzle({ client });
 
  return dbInstance;
}
  1. 生成数据库迁移文件

如果你希望后续通过迁移文件更新数据库表。你可以在完成以上三步之后,在项目根目录下执行以下命令生成数据库迁移文件。

pnpm db:generate

在 ShipAny 中配置不同类型的数据库非常简单,只需要完成以上四个步骤的自定义即可。src/model 下操作数据库的逻辑无需修改。

参考