Skip to main content

如何在聊天应用中使用 AI SDK、Prisma 和 Next.js

20 min

介绍

¥Introduction

Prisma ORM 通过类型安全查询简化了数据库访问,与 Next.jsAI SDK 结合使用,为构建具有持久存储的 AI 聊天应用奠定了坚实的基础。

¥Prisma ORM streamlines database access with type-safe queries, and when paired with Next.js and AI SDK, it creates a powerful foundation for building AI-powered chat applications with persistent storage.

在本指南中,你将学习如何使用 AI SDK、Next.js 和 Prisma ORM 构建聊天应用,将聊天会话和消息存储在 Prisma Postgres 数据库中。你可以在 GitHub 上找到本指南的完整示例。

¥In this guide, you'll learn to build a chat application using AI SDK with Next.js and Prisma ORM to store chat sessions and messages in a Prisma Postgres database. You can find a complete example of this guide on GitHub.

先决条件

¥Prerequisites

1. 设置你的项目

¥ Set up your project

首先,你需要创建一个新的 Next.js 项目。

¥To get started, you'll need to create a new Next.js project.

npx create-next-app@latest ai-sdk-prisma

它会提示你自定义设置。选择默认值:

¥It will prompt you to customize your setup. Choose the defaults:

信息
  • 你想使用 TypeScript 吗?Yes

    ¥Would you like to use TypeScript? Yes

  • 你想使用 ESLint 吗?Yes

    ¥Would you like to use ESLint? Yes

  • 你想使用 Tailwind CSS 吗?Yes

    ¥Would you like to use Tailwind CSS? Yes

  • 你想将代码放在 src/ 目录中吗?No

    ¥Would you like your code inside a src/ directory? No

  • 你想使用 App Router 吗?(推荐)Yes

    ¥Would you like to use App Router? (recommended) Yes

  • 你想为 next dev 使用 Turbopack 吗?Yes

    ¥Would you like to use Turbopack for next dev? Yes

  • 你想自定义导入别名(默认为 @/*)吗?No

    ¥Would you like to customize the import alias (@/* by default)? No

导航到项目目录:

¥Navigate to the project directory:

cd ai-sdk-prisma

2. 安装和配置 Prisma

¥ Install and Configure Prisma

2.1.安装依赖

¥2.1. Install dependencies

要开始使用 Prisma,你需要安装一些依赖:

¥To get started with Prisma, you'll need to install a few dependencies:

npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @prisma/client

安装完成后,请在你的项目中初始化 Prisma:

¥Once installed, initialize Prisma in your project:

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

在设置 Prisma Postgres 数据库时,你需要回答几个问题。选择离你最近的区域,并为数据库选择一个容易记住的名称,例如 "我的 Next.js AI SDK 项目"

¥You'll need to answer a few questions while setting up your Prisma Postgres database. Select the region closest to your location and a memorable name for your database like "My Next.js AI SDK Project"

这将造成:

¥This will create:

  • 一个包含 schema.prisma 文件的 prisma 目录。

    ¥A prisma directory with a schema.prisma file.

  • 一个 Prisma Postgres 数据库。

    ¥A Prisma Postgres database.

  • 一个位于项目根目录、包含 DATABASE_URL 文件的 .env 文件。

    ¥A .env file containing the DATABASE_URL at the project root.

  • output 字段指定生成的 Prisma 客户端的存储位置。

    ¥The output field specifies where the generated Prisma Client will be stored.

2.2.定义 Prisma Schema

¥2.2. Define your Prisma Schema

prisma/schema.prisma 文件中,添加以下模型:

¥In the prisma/schema.prisma file, add the following models:

prisma/schema.prisma
generator client {
provider = "prisma-client-js"
output = "../app/generated/prisma"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Session {
id String @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
messages Message[]
}

model Message {
id String @id @default(cuid())
role MessageRole
content String
createdAt DateTime @default(now())
sessionId String
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
}

enum MessageRole {
USER
ASSISTANT
}

这将创建三个模型:SessionMessageMessageRole

¥This creates three models: Session, Message, and MessageRole.

2.3.配置 Prisma 客户端生成器

¥2.3. Configure the Prisma Client generator

现在,运行以下命令创建数据库表并生成 Prisma 客户端:

¥Now, run the following command to create the database tables and generate the Prisma Client:

npx prisma migrate dev --name init

3. 将 Prisma 集成到 Next.js

¥ Integrate Prisma into Next.js

在其中创建一个 /lib 目录和一个 prisma.ts 文件。此文件将用于创建和导出你的 Prisma 客户端实例。

¥Create a /lib directory and a prisma.ts file inside it. This file will be used to create and export your Prisma Client instance.

mkdir lib
touch lib/prisma.ts

按如下方式设置 Prisma 客户端:

¥Set up the Prisma client like this:

lib/prisma.ts
import { PrismaClient } from "../app/generated/prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";

const globalForPrisma = global as unknown as {
prisma: PrismaClient;
};

const prisma = globalForPrisma.prisma || new PrismaClient().$extends(withAccelerate());

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

export default prisma;
警告

我们建议使用连接池(例如 Prisma 加速)来高效管理数据库连接。

¥We recommend using a connection pooler (like Prisma Accelerate) to manage database connections efficiently.

如果你选择不使用 PrismaClient,请避免在长期存在的环境中全局实例化 PrismaClient。请改为按请求创建并释放客户端,以防止耗尽数据库连接。

¥If you choose not to use one, avoid instantiating PrismaClient globally in long-lived environments. Instead, create and dispose of the client per request to prevent exhausting your database connections.

4. 设置 AI SDK

¥ Set up AI SDK

4.1.安装 AI SDK 并获取 API 密钥

¥4.1. Install AI SDK and get an API key

安装 AI SDK 包:

¥Install the AI SDK package:

npm install ai @ai-sdk/react @ai-sdk/openai zod

要使用 AI SDK,你需要从 OpenAI 获取 API 密钥。

¥To use AI SDK, you'll need to obtain an API key from OpenAI.

  1. 导航到 OpenAI API 密钥

    ¥Navigate to OpenAI API Keys

  2. 点击 Create new secret key

    ¥Click on Create new secret key

  3. 填写表单:

    ¥Fill in the form:

    • 给你的密钥起一个类似 Next.js AI SDK Project 的名字

      ¥Give your key a name like Next.js AI SDK Project

    • 选择 All 访问权限

      ¥Select All access

  4. 点击 Create secret key

    ¥Click on Create secret key

  5. 复制 API 密钥

    ¥Copy the API key

  6. 将 API 密钥添加到 .env 文件:

    ¥Add the API key to the .env file:

.env
DATABASE_URL=<YOUR_DATABASE_URL_HERE>
OPENAI_API_KEY=<YOUR_OPENAI_API_KEY_HERE>

4.2.创建路由处理程序

¥4.2. Create a route handler

你需要创建一个路由处理程序来处理 AI SDK 请求。此处理程序将处理聊天消息并将 AI 响应流式传输回客户端。

¥You need to create a route handler to handle the AI SDK requests. This handler will process chat messages and stream AI responses back to the client.

mkdir -p app/api/chat
touch app/api/chat/route.ts

设置基本路由处理程序:

¥Set up the basic route handler:

app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, UIMessage, convertToModelMessages } from 'ai';

export const maxDuration = 300;

export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();

const result = streamText({
model: openai('gpt-4o'),
messages: convertToModelMessages(messages),
});

return result.toUIMessageStreamResponse();
}

此路由处理程序:

¥This route handler:

  1. 从请求正文中提取对话历史记录

    ¥Extracts the conversation history from the request body

  2. 将 UI 消息转换为 AI 模型所需的格式

    ¥Converts UI messages to the format expected by the AI model

  3. 将 AI 响应实时流式传输回客户端

    ¥Streams the AI response back to the client in real-time

要将聊天会话和消息保存到数据库,我们需要:

¥To save chat sessions and messages to the database, we need to:

  1. 在请求中添加会话 id 参数

    ¥Add a session id parameter to the request

  2. 在响应中包含 onFinish 回调

    ¥Include an onFinish callback in the response

  3. idmessages 参数传递给 saveChat 函数(我们将在下一步构建该函数)

    ¥Pass the id and messages parameters to the saveChat function (which we'll build next)

app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { saveChat } from "@/lib/save-chat";

export const maxDuration = 300;

export async function POST(req: Request) {
const { messages, id }: { messages: UIMessage[]; id: string } = await req.json();

const result = streamText({
model: openai("gpt-4o"),
messages: convertToModelMessages(messages),
});

return result.toUIMessageStreamResponse({
originalMessages: messages,
onFinish: async ({ messages }) => {
await saveChat(messages, id);
},
});
}

4.3.创建 saveChat 函数

¥4.3. Create a saveChat function

lib/save-chat.ts 文件夹下创建一个新文件,用于将聊天会话和消息保存到数据库:

¥Create a new file at lib/save-chat.ts to save the chat sessions and messages to the database:

touch lib/save-chat.ts

首先,创建一个名为 saveChat 的基本函数,用于将聊天会话和消息保存到数据库。

¥To start, create a basic function called saveChat that will be used to save the chat sessions and messages to the database.

messagesid 参数分别类型为 UIMessage[]string 传递给它:

¥Pass into it the messages and id parameters typed as UIMessage[] and string respectively:

lib/save-chat.ts
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {

}

现在,添加逻辑以使用给定的 id 创建会话:

¥Now, add the logic to create a session with the given id:

lib/save-chat.ts
import prisma from "./prisma";
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {
const session = await prisma.session.upsert({
where: { id },
update: {},
create: { id },
});

if (!session) throw new Error("Session not found");
}

添加将消息保存到数据库的逻辑。你将只保存最后两条消息(用户和助手的最后一条消息),以避免任何消息重叠。

¥Add the logic to save the messages to the database. You'll only be saving the last two messages (Users and Assistants last messages) to avoid any overlapping messages.

lib/save-chat.ts
import prisma from "./prisma";
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {
const session = await prisma.session.upsert({
where: { id },
update: {},
create: { id },
});

if (!session) throw new Error("Session not found");

const lastTwoMessages = messages.slice(-2);

for (const msg of lastTwoMessages) {
let content = JSON.stringify(msg.parts);
if (msg.role === "assistant") {
const textParts = msg.parts.filter((part) => part.type === "text");
content = JSON.stringify(textParts);
}

await prisma.message.create({
data: {
role: msg.role === "user" ? "USER" : "ASSISTANT",
content: content,
sessionId: session.id,
},
});
}
}

此函数:

¥This function:

  1. 如果会话不存在,则使用给定的 id 更新会话以创建会话。

    ¥Upserts a session with the given id to create a session if it doesn't exist

  2. 将消息保存到 sessionId 下的数据库中

    ¥Saves the messages to the database under the sessionId

5. 创建消息 API 路由

¥ Create a messages API route

app/api/messages/route.ts 文件夹下创建一个新文件,用于从数据库获取消息:

¥Create a new file at app/api/messages/route.ts to fetch the messages from the database:

mkdir -p app/api/messages
touch app/api/messages/route.ts

创建一个基本的 API 路由,用于从数据库获取消息。

¥Create a basic API route to fetch the messages from the database.

app/api/messages/route.ts
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";

export async function GET() {
try {
const messages = await prisma.message.findMany({
orderBy: { createdAt: "asc" },
});

const uiMessages = messages.map((msg) => ({
id: msg.id,
role: msg.role.toLowerCase(),
parts: JSON.parse(msg.content),
}));

return NextResponse.json({ messages: uiMessages });
} catch (error) {
console.error("Error fetching messages:", error);
return NextResponse.json({ messages: [] });
}
}

6. 创建 UI

¥ Create the UI

app/page.tsx 文件的内容替换为以下内容:

¥Replace the content of the app/page.tsx file with the following:

app/page.tsx
'use client';

export default function Page() {

}

6.1.设置基本导入和状态

¥6.1. Set up the basic imports and state

首先导入所需的依赖并设置用于管理聊天界面的状态变量:

¥Start by importing the required dependencies and setting up the state variables that will manage the chat interface:

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();
}

6.2.加载现有消息

¥6.2. Load existing messages

创建一个 useEffect 钩子,用于在聊天组件加载时自动获取并显示所有之前保存的消息:

¥Create a useEffect hook that will automatically fetch and display any previously saved messages when the chat component loads:

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();

useEffect(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => {
if (data.messages && data.messages.length > 0) {
setMessages(data.messages);
}
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}, [setMessages]);
}

当组件首次安装时,这会从数据库中加载所有现有消息,以便用户可以查看他们之前的对话历史记录。

¥This loads any existing messages from your database when the component first mounts, so users can see their previous conversation history.

6.3.添加消息显示

¥6.3. Add message display

构建 UI 组件,这些组件将在获取数据时显示加载指示器,并以适当的样式呈现聊天消息:

¥Build the UI components that will show a loading indicator while fetching data and render the chat messages with proper styling:

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();

useEffect(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => {
if (data.messages && data.messages.length > 0) {
setMessages(data.messages);
}
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}, [setMessages]);

if (isLoading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}

return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map(message => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} mb-4`}>
<div className={`max-w-[80%] rounded-lg px-4 py-3 ${
message.role === 'user'
? 'bg-neutral-600 text-white'
: 'bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100'
}`}>
<div className="whitespace-pre-wrap">
<p className="text-xs font-extralight mb-1 opacity-70">{message.role === 'user' ? 'YOU ' : 'AI '}</p>
{message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
</div>
</div>
))}

消息渲染逻辑处理不同的消息类型并应用适当的样式。 - 用户消息以深色背景显示在右侧,而 AI 响应以浅色背景显示在左侧。

¥The message rendering logic handles different message types and applies appropriate styling - user messages appear on the right with a dark background, while AI responses appear on the left with a light background.

6.4.添加输入表单

¥6.4. Add the input form

现在我们需要创建输入界面,允许用户输入并向 AI 发送消息:

¥Now we need to create the input interface that allows users to type and send messages to the AI:

app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';
import { useState, useEffect } from 'react';

export default function Chat() {
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(true);

const { messages, sendMessage, setMessages } = useChat();

useEffect(() => {
fetch('/api/messages')
.then(res => res.json())
.then(data => {
if (data.messages && data.messages.length > 0) {
setMessages(data.messages);
}
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}, [setMessages]);

if (isLoading) {
return <div className="flex justify-center items-center h-screen">Loading...</div>;
}

return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map(message => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'} mb-4`}>
<div className={`max-w-[80%] rounded-lg px-4 py-3 ${
message.role === 'user'
? 'bg-neutral-600 text-white'
: 'bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100'
}`}>
<div className="whitespace-pre-wrap">
<p className="text-xs font-extralight mb-1 opacity-70">{message.role === 'user' ? 'YOU ' : 'AI '}</p>
{message.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>;
}
})}
</div>
</div>
</div>
))}

<form
onSubmit={e => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}
>
<input
className="fixed dark:bg-zinc-900 bottom-0 w-full max-w-md p-2 mb-8 border border-zinc-300 dark:border-zinc-800 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={e => setInput(e.currentTarget.value)}
/>
</form>
</div>
);
}

7. 测试你的应用

¥ Test your application

要测试你的应用,请运行以下命令:

¥To test your application, run the following command:

npm run dev

打开浏览器并导航到 http://localhost:3000 以查看你的应用的运行情况。

¥Open your browser and navigate to http://localhost:3000 to see your application in action.

通过向 AI 发送消息来测试它是否已保存到数据库中。检查 Prisma Studio 以查看数据库中的消息。

¥Test it by sending a message to the AI and see if it's saved to the database. Check Prisma Studio to see the messages in the database.

npx prisma studio

你已完成!你刚刚使用 Next.js 和 Prisma 创建了一个 AI SDK 聊天应用。以下是一些后续步骤,以及一些可帮助你开始扩展项目的资源。

¥You're done! You've just created a AI SDK chat application with Next.js and Prisma. Below are some next steps to explore, as well as some more resources to help you get started expanding your project.

后续步骤

¥Next Steps

现在你已经拥有一个连接到 Prisma Postgres 数据库的 AI SDK 聊天应用,你可以:

¥Now that you have a working AI SDK chat application connected to a Prisma Postgres database, you can:

  • 使用更多模型和关系扩展你的 Prisma 模式

    ¥Extend your Prisma schema with more models and relationships

  • 添加创建/更新/删除路由和表单

    ¥Add create/update/delete routes and forms

  • 探索身份验证和验证

    ¥Explore authentication and validation

  • 使用 Prisma Postgres 启用查询缓存以获得更好的性能

    ¥Enable query caching with Prisma Postgres for better performance

更多信息

¥More Info


Stay connected with Prisma

Continue your Prisma journey by connecting with our active community. Stay informed, get involved, and collaborate with other developers:

We genuinely value your involvement and look forward to having you as part of our community!