# AI SDK (with Next.js) (/docs/guides/integrations/ai-sdk)

Location: Guides > Integrations > AI SDK (with Next.js)

Introduction [#introduction]

Prisma ORM streamlines database access with type-safe queries, and when paired with [Next.js](https://nextjs.org/) and [AI SDK](https://sdk.vercel.ai/), it creates a powerful foundation for building AI-powered chat applications with persistent storage.

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](https://github.com/prisma/prisma-examples/tree/latest/orm/ai-sdk-nextjs).

Prerequisites [#prerequisites]

* [Node.js 20+](https://nodejs.org)
* An [OpenAI API key](https://platform.openai.com/api-keys) or other AI provider API key

1. Set up your project [#1-set-up-your-project]

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

  

#### npm

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

#### pnpm

```bash
pnpm dlx create-next-app@latest ai-sdk-prisma
```

#### yarn

```bash
yarn dlx create-next-app@latest ai-sdk-prisma
```

#### bun

```bash
bunx --bun create-next-app@latest ai-sdk-prisma
```

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

> [!NOTE]
> * *Would you like to use TypeScript?* `Yes`
> * *Would you like to use ESLint?* `Yes`
> * *Would you like to use Tailwind CSS?* `Yes`
> * *Would you like your code inside a `src/` directory?* `No`
> * *Would you like to use App Router?* (recommended) `Yes`
> * *Would you like to use Turbopack for `next dev`?* `Yes`
> * \_Would you like to customize the import alias (`@/_`by default)?\*`No`

Navigate to the project directory:

```bash
cd ai-sdk-prisma
```

2. Install and Configure Prisma [#2-install-and-configure-prisma]

2.1. Install dependencies [#21-install-dependencies]

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

  

#### npm

```bash
npm install prisma tsx @types/pg --save-dev
```

#### pnpm

```bash
pnpm add prisma tsx @types/pg --save-dev
```

#### yarn

```bash
yarn add prisma tsx @types/pg --dev
```

#### bun

```bash
bun add prisma tsx @types/pg --dev
```

  

#### npm

```bash
npm install @prisma/client @prisma/adapter-pg dotenv pg
```

#### pnpm

```bash
pnpm add @prisma/client @prisma/adapter-pg dotenv pg
```

#### yarn

```bash
yarn add @prisma/client @prisma/adapter-pg dotenv pg
```

#### bun

```bash
bun add @prisma/client @prisma/adapter-pg dotenv pg
```

> [!NOTE]
> If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of `@prisma/adapter-pg`. For more information, see [Database drivers](/orm/core-concepts/supported-databases/database-drivers).

Once installed, initialize Prisma in your project:

  

#### npm

```bash
npx prisma init --output ../app/generated/prisma
```

#### pnpm

```bash
pnpm dlx prisma init --output ../app/generated/prisma
```

#### yarn

```bash
yarn dlx prisma init --output ../app/generated/prisma
```

#### bun

```bash
bunx --bun prisma init --output ../app/generated/prisma
```

> [!NOTE]
> `prisma init` creates the Prisma scaffolding and a local `DATABASE_URL`. In the next step, you will create a Prisma Postgres database and replace that value with a direct `postgres://...` connection string.

This will create:

* A `prisma` directory with a `schema.prisma` file.
* A `prisma.config.ts` file for configuring Prisma
* A `.env` file containing a local `DATABASE_URL` at the project root.
* The `output` field specifies where the generated Prisma Client will be stored.

Create a Prisma Postgres database and replace the generated `DATABASE_URL` in your `.env` file with the `postgres://...` connection string from the CLI output:

  

#### npm

```bash
npx create-db
```

#### pnpm

```bash
pnpm dlx create-db
```

#### yarn

```bash
yarn dlx create-db
```

#### bun

```bash
bunx --bun create-db
```

2.2. Define your Prisma Schema [#22-define-your-prisma-schema]

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

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

datasource db {
  provider = "postgresql"
}

model Session { // [!code ++]
  id        String    @id // [!code ++]
  createdAt DateTime  @default(now()) // [!code ++]
  updatedAt DateTime  @updatedAt // [!code ++]
  messages  Message[] // [!code ++]
} // [!code ++]
 // [!code ++]
model Message { // [!code ++]
  id        String      @id @default(cuid()) // [!code ++]
  role      MessageRole // [!code ++]
  content   String // [!code ++]
  createdAt DateTime    @default(now()) // [!code ++]
  sessionId String // [!code ++]
  session   Session     @relation(fields: [sessionId], references: [id], onDelete: Cascade) // [!code ++]
} // [!code ++]
 // [!code ++]
enum MessageRole { // [!code ++]
  USER // [!code ++]
  ASSISTANT // [!code ++]
} // [!code ++]
```

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

2.3 Add dotenv to prisma.config.ts [#23-add-dotenv-to-prismaconfigts]

To get access to the variables in the `.env` file, they can either be loaded by your runtime, or by using `dotenv`.
Include an import for `dotenv` at the top of the `prisma.config.ts`

```ts
import "dotenv/config"; // [!code ++]
import { defineConfig, env } from "prisma/config";
export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
});
```

2.4. Configure the Prisma Client generator [#24-configure-the-prisma-client-generator]

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

  

#### npm

```bash
npx prisma migrate dev --name init
```

#### pnpm

```bash
pnpm dlx prisma migrate dev --name init
```

#### yarn

```bash
yarn dlx prisma migrate dev --name init
```

#### bun

```bash
bunx --bun prisma migrate dev --name init
```

  

#### npm

```bash
npx prisma generate
```

#### pnpm

```bash
pnpm dlx prisma generate
```

#### yarn

```bash
yarn dlx prisma generate
```

#### bun

```bash
bunx --bun prisma generate
```

3. Integrate Prisma into Next.js [#3-integrate-prisma-into-nextjs]

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

```bash
mkdir lib
touch lib/prisma.ts
```

Set up the Prisma client like this:

```tsx title="lib/prisma.ts"
import { PrismaClient } from "../app/generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";

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

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

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

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

export default prisma;
```

> [!WARNING]
> We recommend using a connection pooler (like [Prisma Accelerate](https://www.prisma.io/accelerate)) to manage database connections efficiently.
> 
> 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. Set up AI SDK [#4-set-up-ai-sdk]

4.1. Install AI SDK and get an API key [#41-install-ai-sdk-and-get-an-api-key]

Install the AI SDK package:

  

#### npm

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

#### pnpm

```bash
pnpm add ai @ai-sdk/react @ai-sdk/openai zod
```

#### yarn

```bash
yarn add ai @ai-sdk/react @ai-sdk/openai zod
```

#### bun

```bash
bun add ai @ai-sdk/react @ai-sdk/openai zod
```

To use AI SDK, you'll need to obtain an API key from [OpenAI](https://platform.openai.com/api-keys).

1. Navigate to [OpenAI API Keys](https://platform.openai.com/api-keys)
2. Click on `Create new secret key`
3. Fill in the form:
   * Give your key a name like `Next.js AI SDK Project`
   * Select `All` access
4. Click on `Create secret key`
5. Copy the API key
6. Add the API key to the `.env` file:

```text title=".env"
DATABASE_URL=<YOUR_DATABASE_URL_HERE>
OPENAI_API_KEY=<YOUR_OPENAI_API_KEY_HERE>
```

4.2. Create a route handler [#42-create-a-route-handler]

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.

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

Set up the basic route handler:

```tsx title="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. Converts UI messages to the format expected by the AI model
3. Streams the AI response back to the client in real-time

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

1. Add a session `id` parameter to the request
2. Include an `onFinish` callback in the response
3. Pass the `id` and `messages` parameters to the `saveChat` function (which we'll build next)

```tsx title="app/api/chat/route.ts"
import { openai } from "@ai-sdk/openai";
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { saveChat } from "@/lib/save-chat"; // [!code ++]

export const maxDuration = 300;

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

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

  return result.toUIMessageStreamResponse({
    originalMessages: messages, // [!code ++]
    onFinish: async ({ messages }) => {
      // [!code ++]
      await saveChat(messages, id); // [!code ++]
    }, // [!code ++]
  });
}
```

4.3. Create a saveChat function [#43-create-a-savechat-function]

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

```bash
touch lib/save-chat.ts
```

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

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

```tsx title="lib/save-chat.ts"
import { UIMessage } from "ai";

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

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

```tsx title="lib/save-chat.ts"
import prisma from "./prisma"; // [!code ++]
import { UIMessage } from "ai";

export async function saveChat(messages: UIMessage[], id: string) {
  const session = await prisma.session.upsert({
    // [!code ++]
    where: { id }, // [!code ++]
    update: {}, // [!code ++]
    create: { id }, // [!code ++]
  }); // [!code ++]
  // [!code ++]
  if (!session) throw new Error("Session not found"); // [!code ++]
}
```

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.

```tsx title="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); // [!code ++]
  // [!code ++]
  for (const msg of lastTwoMessages) {
    // [!code ++]
    let content = JSON.stringify(msg.parts); // [!code ++]
    if (msg.role === "assistant") {
      // [!code ++]
      const textParts = msg.parts.filter((part) => part.type === "text"); // [!code ++]
      content = JSON.stringify(textParts); // [!code ++]
    } // [!code ++]
    // [!code ++]
    await prisma.message.create({
      // [!code ++]
      data: {
        // [!code ++]
        role: msg.role === "user" ? "USER" : "ASSISTANT", // [!code ++]
        content: content, // [!code ++]
        sessionId: session.id, // [!code ++]
      }, // [!code ++]
    }); // [!code ++]
  } // [!code ++]
}
```

This function:

1. Upserts a session with the given `id` to create a session if it doesn't exist
2. Saves the messages to the database under the `sessionId`

5. Create a messages API route [#5-create-a-messages-api-route]

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

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

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

```tsx title="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. Create the UI [#6-create-the-ui]

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

```tsx title="app/page.tsx"
"use client";

export default function Page() {}
```

6.1. Set up the basic imports and state [#61-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:

```tsx title="app/page.tsx"
"use client";

import { useChat } from "@ai-sdk/react"; // [!code ++]
import { useState, useEffect } from "react"; // [!code ++]

export default function Chat() {
  const [input, setInput] = useState(""); // [!code ++]
  const [isLoading, setIsLoading] = useState(true); // [!code ++]
  // [!code ++]
  const { messages, sendMessage, setMessages } = useChat(); // [!code ++]
}
```

6.2. Load existing messages [#62-load-existing-messages]

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

```tsx title="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(() => {
    // [!code ++]
    fetch("/api/messages") // [!code ++]
      .then((res) => res.json()) // [!code ++]
      .then((data) => {
        // [!code ++]
        if (data.messages && data.messages.length > 0) {
          // [!code ++]
          setMessages(data.messages); // [!code ++]
        } // [!code ++]
        setIsLoading(false); // [!code ++]
      }) // [!code ++]
      .catch(() => setIsLoading(false)); // [!code ++]
  }, [setMessages]); // [!code ++]
}
```

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

6.3. Add message display [#63-add-message-display]

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

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

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. Add the input form [#64-add-the-input-form]

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

```tsx title="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 // [!code ++]
        onSubmit={(e) => {
          // [!code ++]
          e.preventDefault(); // [!code ++]
          sendMessage({ text: input }); // [!code ++]
          setInput(""); // [!code ++]
        }} // [!code ++]
      >
        {" "}
        // [!code ++]
        <input // [!code ++]
          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" // [!code ++]
          value={input} // [!code ++]
          placeholder="Say something..." // [!code ++]
          onChange={(e) => setInput(e.currentTarget.value)} // [!code ++]
        />{" "}
        // [!code ++]
      </form>{" "}
      // [!code ++]
    </div>
  );
}
```

7. Test your application [#7-test-your-application]

To test your application, run the following command:

  

#### npm

```bash
npm run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### bun

```bash
bun run dev
```

Open your browser and navigate to [`http://localhost:3000`](http://localhost:3000) to see your application in action.

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.

  

#### npm

```bash
npx prisma studio
```

#### pnpm

```bash
pnpm dlx prisma studio
```

#### yarn

```bash
yarn dlx prisma studio
```

#### bun

```bash
bunx --bun prisma studio
```

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 [#next-steps]

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

* Extend your Prisma schema with more models and relationships
* Add create/update/delete routes and forms
* Explore authentication and validation

More Info [#more-info]

* [Prisma Documentation](/orm)
* [AI SDK Documentation](https://ai-sdk.dev/)

## Related pages

- [`Datadog`](https://www.prisma.io/docs/guides/integrations/datadog): Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog
- [`Embedded Prisma Studio (with Next.js)`](https://www.prisma.io/docs/guides/integrations/embed-studio): Learn how to embed Prisma Studio directly in your Next.js application for database management
- [`GitHub Actions`](https://www.prisma.io/docs/guides/integrations/github-actions): Provision and manage Prisma Postgres databases per pull request using GitHub Actions and Prisma Management API
- [`Neon with Accelerate`](https://www.prisma.io/docs/guides/integrations/neon-accelerate): Learn how to set up PostgreSQL on Neon with Prisma Accelerate's Connection Pool
- [`Permit.io`](https://www.prisma.io/docs/guides/integrations/permit-io): Learn how to implement access control with Prisma ORM with Permit.io