跳至主要内容

如何在 Better Auth 和 Astro 中使用 Prisma ORM

25 分钟

介绍

Better Auth 是一个现代化的开源 Web 应用程序身份验证解决方案。它采用 TypeScript 构建,提供简单且可扩展的身份验证体验,并支持包括 Prisma 在内的多种数据库适配器。

在本指南中,您将 Better Auth 集成到一个全新的 Astro 应用程序中,并将用户持久化到 Prisma Postgres 数据库中。您可以在 GitHub 上找到本指南的完整示例。

先决条件

1. 设置项目

创建一个新的 Astro 项目

npm create astro@latest betterauth-astro-prisma
信息
  • 您希望如何开始您的新项目? 使用最小(空)模板
  • 安装依赖项?(推荐) Yes
  • 初始化一个新的 Git 仓库?(可选)

导航到项目目录

cd betterauth-astro-prisma

这些选择将创建一个带有 TypeScript 的最小 Astro 项目,以确保类型安全。

2. 设置 Prisma

接下来,您将向项目中添加 Prisma 以管理您的数据库。

2.1. 安装 Prisma 和依赖项

安装必要的 Prisma 包

npm install prisma tsx @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv pg
信息

如果你使用的是其他数据库提供程序(MySQL、SQL Server、SQLite),请安装相应的驱动程序适配器包,而不是 @prisma/adapter-pg。有关更多信息,请参阅 数据库驱动程序

安装后,在项目中初始化 Prisma

npx prisma init --db --output ../prisma/generated
信息

在设置 Prisma Postgres 数据库时,您需要回答几个问题。选择离您位置最近的区域,并为您的数据库选择一个易于记忆的名称,例如“我的 Better Auth Astro 项目”。

这将创建

  • 一个包含 `schema.prisma` 文件的 `prisma` 目录
  • 一个 Prisma Postgres 数据库
  • 项目根目录下包含 `DATABASE_URL` 的 `.env` 文件
  • 一个用于配置 Prisma 的 prisma.config.ts 文件
  • 用于生成 Prisma 客户端的 `output` 目录,路径为 `prisma/generated`

2.2. 配置 Prisma 以加载环境变量

为了访问 `.env` 文件中的变量,请更新 `prisma.config.ts` 以导入 `dotenv`

prisma.config.ts
import "dotenv/config";
import { defineConfig, env } from "prisma/config";

export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
engine: "classic",
datasource: {
url: env("DATABASE_URL"),
},
});

2.3. 生成 Prisma 客户端

运行以下命令生成 Prisma 客户端

npx prisma generate

2.4. 设置全局 Prisma 客户端

在 `src` 目录中,创建一个 `lib` 文件夹,并在其中创建一个 `prisma.ts` 文件。该文件将用于创建和导出您的 Prisma 客户端实例。

mkdir -p src/lib
touch src/lib/prisma.ts

像这样设置 Prisma 客户端

src/lib/prisma.ts
import { PrismaClient } from "../../prisma/generated/client";
import { PrismaPg } from "@prisma/adapter-pg";

const adapter = new PrismaPg({
connectionString: import.meta.env.DATABASE_URL,
});

const prisma = new PrismaClient({
adapter,
});

export default prisma;
警告

我们建议使用连接池(如 Prisma Accelerate)来有效地管理数据库连接。

如果您选择不使用一个,请**避免**在长生命周期环境中全局实例化 PrismaClient。相反,请为每个请求创建和处置客户端,以防止耗尽数据库连接。

3. 设置 Better Auth

现在是时候集成 Better Auth 进行身份验证了。

3.1. 安装并配置 Better Auth

首先,安装 Better Auth 核心包

npm install better-auth

接下来,生成 Better Auth 将用于签署身份验证令牌的安全密钥。这确保了您的令牌不会被篡改。

npx @better-auth/cli@latest secret

复制生成的密钥,并将其与您的应用程序 URL 一起添加到您的 `.env` 文件中。

.env
# Better Auth
BETTER_AUTH_SECRET=your-generated-secret
BETTER_AUTH_URL=https://:4321

# Prisma
DATABASE_URL="your-database-url"
信息

Astro 的默认开发服务器在端口 `4321` 上运行。如果您的应用程序在不同的端口上运行,请相应地更新 `BETTER_AUTH_URL`。

现在,为 Better Auth 创建一个配置文件。在 `src/lib` 目录中,创建一个 `auth.ts` 文件。

touch src/lib/auth.ts

在此文件中,您将配置 Better Auth 使用 Prisma 适配器,该适配器允许它将用户和会话数据持久化到您的数据库中。您还将启用电子邮件和密码身份验证。

src/lib/auth.ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import prisma from "./prisma";

export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
emailAndPassword: {
enabled: true,
},
});

Better Auth 还支持其他登录方法,例如社交登录(Google、GitHub 等),您可以在其文档中探索。

3.2. 将 Better Auth 模型添加到您的模式

Better Auth 提供了一个 CLI 命令,可以自动将必要的身份验证模型(`User`、`Session`、`Account` 和 `Verification`)添加到您的 `schema.prisma` 文件中。

运行以下命令

npx @better-auth/cli generate
注意

它将询问是否确认覆盖您现有的 Prisma schema。选择 `y`。

这将添加以下模型

model User {
id String @id
name String
email String
emailVerified Boolean
image String?
createdAt DateTime
updatedAt DateTime
sessions Session[]
accounts Account[]

@@unique([email])
@@map("user")
}

model Session {
id String @id
expiresAt DateTime
token String
createdAt DateTime
updatedAt DateTime
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)

@@unique([token])
@@map("session")
}

model Account {
id String @id
accountId String
providerId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime
updatedAt DateTime

@@map("account")
}

model Verification {
id String @id
identifier String
value String
expiresAt DateTime
createdAt DateTime?
updatedAt DateTime?

@@map("verification")
}

3.3. 迁移数据库

随着 schema 中新模型的添加,您需要更新数据库。运行迁移以创建相应的表

npx prisma migrate dev --name add-auth-models
npx prisma generate

4. 设置 API 路由

Better Auth 需要一个 API 端点来处理身份验证请求,如登录、注册和注销。您将在 Astro 中创建一个全功能 API 路由来处理所有发送到 `/api/auth/[...all]` 的请求。

在 `src/pages` 目录中,创建一个 `api/auth` 文件夹结构,并在其中创建一个 `[...all].ts` 文件。

mkdir -p src/pages/api/auth
touch 'src/pages/api/auth/[...all].ts'

将以下代码添加到新创建的 `[...all].ts` 文件中。此代码使用 Better Auth 处理程序来处理身份验证请求。

src/pages/api/auth/[...all].ts
import { auth } from "../../../lib/auth";
import type { APIRoute } from "astro";

export const prerender = false; // Not needed in 'server' mode

export const ALL: APIRoute = async (ctx) => {
return auth.handler(ctx.request);
};

接下来,您需要一个客户端工具来与 Astro 页面中的这些端点进行交互。在 `src/lib` 目录中,创建一个 `auth-client.ts` 文件。

touch src/lib/auth-client.ts

添加以下代码,它将创建您将在 UI 中使用的客户端函数。

src/lib/auth-client.ts
import { createAuthClient } from "better-auth/client";

export const authClient = createAuthClient();

export const { signIn, signUp, signOut, useSession } = authClient;

5. 配置 TypeScript 定义

在 `src` 目录中,创建一个 `env.d.ts` 文件,为环境变量和 Astro local 提供 TypeScript 定义。

touch src/env.d.ts

添加以下类型定义

src/env.d.ts
/// <reference path="../.astro/types.d.ts" />

declare namespace App {
interface Locals {
user: import("better-auth").User | null;
session: import("better-auth").Session | null;
}
}

interface ImportMetaEnv {
readonly DATABASE_URL: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}

6. 设置认证中间件

在 `src` 目录中,创建一个 `middleware.ts` 文件,用于检查每个请求的身份验证状态。这将使用户和会话数据可用于所有页面。

touch src/middleware.ts

添加以下代码

src/middleware.ts
import { auth } from "./lib/auth";
import { defineMiddleware } from "astro:middleware";

export const onRequest = defineMiddleware(async (context, next) => {
context.locals.user = null;
context.locals.session = null;
const isAuthed = await auth.api.getSession({
headers: context.request.headers,
});
if (isAuthed) {
context.locals.user = isAuthed.user;
context.locals.session = isAuthed.session;
}
return next();
});

7. 设置您的页面

现在,让我们构建身份验证的用户界面。在 `src/pages` 目录中,创建以下文件夹结构

  • sign-up/index.astro
  • sign-in/index.astro
  • dashboard/index.astro
mkdir -p src/pages/{sign-up,sign-in,dashboard}
touch src/pages/{sign-up,sign-in,dashboard}/index.astro

7.1. 注册页面

此页面允许新用户创建账户。首先在 `src/pages/sign-up/index.astro` 中创建基本的 HTML 结构。

src/pages/sign-up/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign Up</title>
</head>
<body>
<main>
<h1>Sign Up</h1>
</main>
</body>
</html>

添加一个包含姓名、电子邮件和密码输入字段的表单。此表单将收集用户的注册信息。

src/pages/sign-up/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign Up</title>
</head>
<body>
<main>
<h1>Sign Up</h1>
<form id="signup-form">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<input
required
type="password"
name="password"
placeholder="Password"
/>
<button type="submit">Sign up</button>
</form>
<p>Already have an account? <a href="/sign-in">Sign in here</a>.</p>
</main>
</body>
</html>

现在添加一个脚本来处理表单提交。导入 `authClient` 并向表单添加一个事件监听器,该监听器将阻止默认提交行为,提取表单数据,并调用 Better Auth 的注册方法。

src/pages/sign-up/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign Up</title>
</head>
<body>
<main>
<h1>Sign Up</h1>
<form id="signup-form">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<input
required
type="password"
name="password"
placeholder="Password"
/>
<button type="submit">Sign up</button>
</form>
<p>Already have an account? <a href="/sign-in">Sign in here</a>.</p>
</main>
<script>
import { authClient } from "../../lib/auth-client";
document
.getElementById("signup-form")
?.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const tmp = await authClient.signUp.email({
name,
email,
password,
});
console.log(tmp);
if (Boolean(tmp.error) === false) window.location.href = "/dashboard";
});
</script>
</body>
</html>

最后,添加一个服务器端检查,以将已通过身份验证的用户从该页面重定向。如果用户已经登录,他们应该被重定向到仪表板。

src/pages/sign-up/index.astro
---
export const prerender = false;

if (Astro.locals.user?.id) return Astro.redirect("/dashboard");
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign Up</title>
</head>
<body>
<main>
<h1>Sign Up</h1>
<form id="signup-form">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<input
required
type="password"
name="password"
placeholder="Password"
/>
<button type="submit">Sign up</button>
</form>
<p>Already have an account? <a href="/sign-in">Sign in here</a>.</p>
</main>
<script>
import { authClient } from "../../lib/auth-client";
document
.getElementById("signup-form")
?.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const tmp = await authClient.signUp.email({
name,
email,
password,
});
console.log(tmp);
if (Boolean(tmp.error) === false) window.location.href = "/dashboard";
});
</script>
</body>
</html>

7.2. 登录页面

此页面允许现有用户进行身份验证。首先在 `src/pages/sign-in/index.astro` 中创建基本的 HTML 结构。

src/pages/sign-in/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign In</title>
</head>
<body>
<main>
<h1>Sign In</h1>
</main>
</body>
</html>

添加一个包含电子邮件和密码输入字段的表单。此表单将收集用户的凭据。

src/pages/sign-in/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign In</title>
</head>
<body>
<main>
<h1>Sign In</h1>
<form id="signin-form">
<input type="email" name="email" placeholder="Email" required />
<input
required
type="password"
name="password"
placeholder="Password"
/>
<button type="submit">Sign In</button>
</form>
<p>Don't have an account? <a href="/sign-up">Sign up here</a>.</p>
</main>
</body>
</html>

现在添加一个脚本来处理表单提交。导入 `authClient` 并添加一个事件监听器,该监听器将阻止默认提交,提取表单数据,并调用 Better Auth 登录方法。

src/pages/sign-in/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign In</title>
</head>
<body>
<main>
<h1>Sign In</h1>
<form id="signin-form">
<input type="email" name="email" placeholder="Email" required />
<input
required
type="password"
name="password"
placeholder="Password"
/>
<button type="submit">Sign In</button>
</form>
<p>Don't have an account? <a href="/sign-up">Sign up here</a>.</p>
</main>
<script>
import { authClient } from "../../lib/auth-client";
document
.getElementById("signin-form")
?.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const tmp = await authClient.signIn.email({
email,
password,
});
if (Boolean(tmp.error) === false) window.location.href = "/dashboard";
});
</script>
</body>
</html>

最后,添加一个服务器端检查,以将已通过身份验证的用户从该页面重定向。如果用户已经登录,他们应该被重定向到仪表板。

src/pages/sign-in/index.astro
---
export const prerender = false;

if (Astro.locals.user?.id) return Astro.redirect("/dashboard");
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Sign In</title>
</head>
<body>
<main>
<h1>Sign In</h1>
<form id="signin-form">
<input type="email" name="email" placeholder="Email" required />
<input
required
type="password"
name="password"
placeholder="Password"
/>
<button type="submit">Sign In</button>
</form>
<p>Don't have an account? <a href="/sign-up">Sign up here</a>.</p>
</main>
<script>
import { authClient } from "../../lib/auth-client";
document
.getElementById("signin-form")
?.addEventListener("submit", async (event) => {
event.preventDefault();
const formData = new FormData(event.target as HTMLFormElement);
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const tmp = await authClient.signIn.email({
email,
password,
});
if (Boolean(tmp.error) === false) window.location.href = "/dashboard";
});
</script>
</body>
</html>

7.3. 仪表盘页面

这是用于已通过身份验证的用户的受保护页面。首先在 `src/pages/dashboard/index.astro` 中创建基本的 HTML 结构。

src/pages/dashboard/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Dashboard</title>
</head>
<body>
<main>
<h1>Dashboard</h1>
</main>
</body>
</html>

添加一个服务器端检查以保护此路由。如果用户未通过身份验证,请将其重定向到登录页面。

src/pages/dashboard/index.astro
---
export const prerender = false;

if (!Astro.locals.user?.id) return Astro.redirect("/sign-in");
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Dashboard</title>
</head>
<body>
<main>
<h1>Dashboard</h1>
</main>
</body>
</html>

现在显示已通过身份验证的用户信息。`Astro.locals.user` 对象包含由中间件设置的用户数据。

src/pages/dashboard/index.astro
---
export const prerender = false;

if (!Astro.locals.user?.id) return Astro.redirect("/sign-in");
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Dashboard</title>
</head>
<body>
<main>
<h1>Dashboard</h1>
<pre>{JSON.stringify(Astro.locals.user, null, 2)}</pre>
</main>
</body>
</html>

最后,添加一个退出按钮。导入 `authClient` 并添加一个调用退出方法的按钮,允许用户注销并被重定向到登录页面。

src/pages/dashboard/index.astro
---
export const prerender = false;

if (!Astro.locals.user?.id) return Astro.redirect("/sign-in");
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Dashboard</title>
</head>
<body>
<main>
<h1>Dashboard</h1>
<pre>{JSON.stringify(Astro.locals.user, null, 2)}</pre>
<button id="signOutButton">Sign Out</button>
</main>
<script>
import { authClient } from "../../lib/auth-client";
document
.getElementById("signOutButton")
?.addEventListener("click", async () => {
await authClient.signOut();
window.location.href = "/sign-in";
});
</script>
</body>
</html>

7.4. 主页

最后,更新主页以提供简单的导航。将 `src/pages/index.astro` 的内容替换为以下内容

src/pages/index.astro
---
export const prerender = false;
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Better Auth + Astro + Prisma</title>
</head>
<body>
<main>
<h1>Better Auth + Astro + Prisma</h1>
{
Astro.locals.user ? (
<div>
<p>Welcome back, {Astro.locals.user.name}!</p>
<a href="/dashboard">Go to Dashboard</a>
</div>
) : (
<div>
<a href="/sign-up">Sign Up</a>
<a href="/sign-in">Sign In</a>
</div>
)
}
</main>
</body>
</html>

8. 测试一下

你的应用程序现在已完全配置。

  1. 启动开发服务器以进行测试
npm run dev
  1. 在浏览器中导航到 `https://:4321`。您应该会看到带有“注册”和“登录”链接的主页。

  2. 点击注册,创建一个新账户,您应该会被重定向到仪表板。然后您可以注销并重新登录。

  3. 要直接在数据库中查看用户数据,您可以使用 Prisma Studio。

npx prisma studio
  1. 这将在浏览器中打开一个新标签页,你可以在其中查看 UserSessionAccount 表及其内容。
成功

恭喜!您现在拥有一个使用 Better Auth、Prisma 和 Astro 构建的完全功能的身份验证系统。

后续步骤

  • 添加对社交登录或魔术链接的支持
  • 实现密码重置和电子邮件验证
  • 添加用户个人资料和帐户管理页面
  • 部署到 Vercel 或 Netlify 并保护您的环境变量
  • 使用自定义应用程序模型扩展您的 Prisma 模式

进一步阅读


与 Prisma 保持联系

通过以下方式与我们保持联系,继续你的 Prisma 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发人员协作。

我们真诚地感谢你的参与,并期待你成为我们社区的一部分!

© . This site is unofficial and not affiliated with Prisma Data, Inc.