从 Supabase Auth 迁移到 Better Auth + PlanetScale PostgreSQL 指南

最近,PlanetScale 宣布 支持 PostgreSQL。这对开发者来说是令人兴奋的消息,也是数据库行业的一个重大进步。

我们注意到,一些用户正在从 Supabase 迁移到 PlanetScale PostgreSQL,但面临挑战,因为他们还依赖 Supabase Auth。此指南将帮助您将认证从 Supabase Auth 迁移到 PlanetScale PostgreSQL 上的 Better Auth。

1. 设置 PlanetScale 数据库

打开 PlanetScale 仪表板

创建一个新数据库

获取您的连接字符串 (PostgreSQL URI)

postgresql://<username>:<password>@<host>/postgres?sslmode=verify-full

将数据库 URL 保存到您的 .env 文件中,以便稍后与 Better Auth 一起使用:

.env
DATABASE_URL =
  postgresql://<username>:<password>@<host>/postgres?sslmode=verify-full

这就是我们的认证配置中的 database 字段的内容

2. 安装 Better Auth

安装 Better Auth

npm install better-auth

按照并完成基本设置 这里

确保按照文档设置所有必需的环境变量。

3. 安装 PostgreSQL 客户端

安装 pg 包及其类型:

npm install pg
npm install --save-dev @types/pg

4. 生成并迁移 Better Auth 架构

运行此 CLI 命令以生成设置 Better Auth 所需的所有架构:

npx @better-auth/cli generate 

然后运行此命令将生成的架构应用到您的 PlanetScale 数据库:

npx @better-auth/cli migrate 

您现在应该在 PlanetScale 中拥有所需的认证表。

5. 快速检查

您的认证配置应该像这样:

import { Pool } from "pg";
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  baseURL: "http://localhost:3000",
  database: new Pool({
    connectionString: process.env.DATABASE_URL,
  }),
  emailAndPassword: {
    enabled: true,
  },
});
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
  baseURL: "http://localhost:3000",
});

export const { signIn, signUp, useSession } = createAuthClient();

6. 有趣的部分

现在进入有趣的部分。您现在已经设置好,可以将认证从 Supabase Auth 迁移到 Better Auth,您只需遍历您使用 Supabase Auth 客户端的实例,并将其替换为 Better Auth 客户端。我们这里将看到一些示例。

// Supabase Auth
await supabase.auth.signUp({
  email,
  password,
});

// Better Auth
await authClient.signUp.email({
  email,
  password,
  name: "John",
});
// Supabase
await supabase.auth.signInWithPassword({
  email,
  password,
});

// Better Auth
await authClient.signIn.email({
  email,
  password,
});
// Supabase
const { data, error } = await supabase.auth.getClaims();

// Better Auth
const { data, error } = await authClient.useSession();

7. 从 Supabase Auth 迁移您的用户

此迁移将使所有活动会话失效。虽然此指南目前未涵盖迁移双因素 (2FA) 或行级别安全 (RLS) 配置,但两者都可以通过额外步骤实现。

有关更详细的指南,请查看我们制作的此 指南

本质上,您应该能够将以下代码复制到 migration.ts 中并运行它。

migration.ts
import { Pool } from "pg";
import { auth } from "./lib/auth";
import { User as SupabaseUser } from "@supabase/supabase-js";

type User = SupabaseUser & {
  is_super_admin: boolean;
  raw_user_meta_data: {
    avatar_url: string;
  };
  encrypted_password: string;
  email_confirmed_at: string;
  created_at: string;
  updated_at: string;
  is_anonymous: boolean;
  identities: {
    provider: string;
    identity_data: {
      sub: string;
      email: string;
    };
    created_at: string;
    updated_at: string;
  };
};

const migrateFromSupabase = async () => {
  const ctx = await auth.$context;
  const db = ctx.options.database as Pool;
  const users = await db
    .query(
      `
			SELECT 
				u.*,
				COALESCE(
					json_agg(
						i.* ORDER BY i.id
					) FILTER (WHERE i.id IS NOT NULL),
					'[]'::json
				) as identities
			FROM auth.users u
			LEFT JOIN auth.identities i ON u.id = i.user_id
			GROUP BY u.id
		`
    )
    .then((res) => res.rows as User[]);
  for (const user of users) {
    if (!user.email) {
      continue;
    }
    await ctx.adapter
      .create({
        model: "user",
        data: {
          id: user.id,
          email: user.email,
          name: user.email,
          role: user.is_super_admin ? "admin" : user.role,
          emailVerified: !!user.email_confirmed_at,
          image: user.raw_user_meta_data.avatar_url,
          createdAt: new Date(user.created_at),
          updatedAt: new Date(user.updated_at),
          isAnonymous: user.is_anonymous,
        },
      })
      .catch(() => {});
    for (const identity of user.identities) {
      const existingAccounts = await ctx.internalAdapter.findAccounts(user.id);

      if (identity.provider === "email") {
        const hasCredential = existingAccounts.find(
          (account: { providerId: string }) =>
            account.providerId === "credential"
        );
        if (!hasCredential) {
          await ctx.adapter
            .create({
              model: "account",
              data: {
                userId: user.id,
                providerId: "credential",
                accountId: user.id,
                password: user.encrypted_password,
                createdAt: new Date(user.created_at),
                updatedAt: new Date(user.updated_at),
              },
            })
            .catch(() => {});
        }
      }
      const supportedProviders = Object.keys(ctx.options.socialProviders || {});
      if (supportedProviders.includes(identity.provider)) {
        const hasAccount = existingAccounts.find(
          (account: { providerId: string }) =>
            account.providerId === identity.provider
        );
        if (!hasAccount) {
          await ctx.adapter.create({
            model: "account",
            data: {
              userId: user.id,
              providerId: identity.provider,
              accountId: identity.identity_data?.sub,
              createdAt: new Date(identity.created_at ?? user.created_at),
              updatedAt: new Date(identity.updated_at ?? user.updated_at),
            },
          });
        }
      }
    }
  }
};
migrateFromSupabase();

运行迁移脚本

Terminal
bun migration.ts # 或使用 node, ts-node 等

8. 迁移您的数据其余部分

如果您在 Supabase 中有额外的用户相关数据,您可以使用 Supabase 到 PlanetScale 迁移工具

9. 从您的代码库中清理所有 Supabase Auth 代码

您现在拥有自己的认证,您应该开始移除所有与 Supabase Auth 相关的代码。

10. 完成!🎉

您已成功从 Supabase Auth 迁移到 PlanetScale 上的 Better Auth。

提示

  • 在生产环境中仔细检查所有环境变量是否已设置。
  • 在上线前测试所有认证流程(注册、登录、密码重置、会话刷新)。
  • 请记住,这只是基础内容,如果您已在许多地方集成了 Supabase Auth 的认证函数,您需要找到合适的 Better Auth 替换项 这里
  • 玩得开心!

了解更多!