Skip to main content

如何在 React Router 7 中使用 Prisma ORM

10 min

介绍

¥Introduction

本指南向你展示如何将 Prisma ORM 与 React Router 7 结合使用。React Router 7 是一个多策略路由,可以像声明式路由一样精简,也可以像全栈框架一样功能齐全。

¥This guide shows you how to use Prisma ORM with React Router 7, a multi-strategy router that can be as minimal as declarative routing or as full-featured as a fullstack framework.

你将学习如何使用 React Router 7 设置 Prisma ORM 和 Prisma Postgres 并处理迁移。你可以找到 GitHub 上的部署就绪示例

¥You'll learn how to set up Prisma ORM and Prisma Postgres with React Router 7 and handle migrations. You can find a deployment-ready example on GitHub.

先决条件

¥Prerequisites

1. 设置你的项目

¥ Set up your project

在你要创建项目的目录中,运行 create-react-router 来创建一个新的 React Router 应用,你将在本指南中使用。

¥From the directory where you want to create your project, run create-react-router to create a new React Router app that you will be using for this guide.

npx create-react-router@latest react-router-7-prisma

系统将提示你选择以下内容,请为两者选择 Yes

¥You'll be prompted to select the following, select Yes for both:

信息
  • 是否初始化新​​的 git 存储库?Yes

    ¥Initialize a new git repository? Yes

  • 使用 npm 安装依赖?Yes

    ¥Install dependencies with npm? Yes

现在,导航到项目目录:

¥Now, navigate to the project directory:

cd react-router-7-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 数据库时,你需要回答几个问题。选择离你最近的区域,并为数据库选择一个容易记住的名称,例如 "我的 React Router 7 项目"

¥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 React Router 7 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.

  • 用于生成的 Prisma 客户端的 output 目录,用作 app/generated/prisma

    ¥An output directory for the generated Prisma Client as app/generated/prisma.

2.2.定义 Prisma Schema

¥2.2. Define your Prisma Schema

prisma/schema.prisma 文件中,添加以下模型,并将生成器更改为使用 prisma-client 提供程序:

¥In the prisma/schema.prisma file, add the following models and change the generator to use the prisma-client provider:

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

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

model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
}

这将创建两个模型:UserPost,它们之间存在一对多关系。

¥This creates two models: User and Post, with a one-to-many relationship between them.

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

2.4.种子数据库

¥2.4. Seed the database

添加一些种子数据,以便使用示例用户和帖子填充数据库。

¥Add some seed data to populate the database with sample users and posts.

prisma/ 目录中创建一个名为 seed.ts 的新文件:

¥Create a new file called seed.ts in the prisma/ directory:

prisma/seed.ts
import { PrismaClient, Prisma } from "../app/generated/prisma/client.js";

const prisma = new PrismaClient();

const userData: Prisma.UserCreateInput[] = [
{
name: "Alice",
email: "alice@prisma.io",
posts: {
create: [
{
title: "Join the Prisma Discord",
content: "https://pris.ly/discord",
published: true,
},
{
title: "Prisma on YouTube",
content: "https://pris.ly/youtube",
},
],
},
},
{
name: "Bob",
email: "bob@prisma.io",
posts: {
create: [
{
title: "Follow Prisma on Twitter",
content: "https://www.twitter.com/prisma",
published: true,
},
],
},
},
];

export async function main() {
for (const u of userData) {
await prisma.user.create({ data: u });
}
}

main();

现在,通过更新 package.json 来告诉 Prisma 如何运行此脚本:

¥Now, tell Prisma how to run this script by updating your package.json:

package.json
{
"name": "react-router-7-prisma",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@react-router/node": "^7.3.0",
"@react-router/serve": "^7.3.0",
"isbot": "^5.1.17",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.3.0"
},
"devDependencies": {
"@react-router/dev": "^7.3.0",
"@tailwindcss/vite": "^4.0.0",
"@types/node": "^20",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"prisma": "^6.5.0",
"react-router-devtools": "^1.1.0",
"tailwindcss": "^4.0.0",
"tsx": "^4.19.3",
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vite-tsconfig-paths": "^5.1.4"
}
}

运行种子脚本:

¥Run the seed script:

npx prisma db seed

打开 Prisma Studio 检查你的数据:

¥And open Prisma Studio to inspect your data:

npx prisma studio

3. 将 Prisma 集成到 React Router 7

¥ Integrate Prisma into React Router 7

3.1.创建 Prisma 客户端

¥3.1. Create a Prisma Client

app 目录中,创建一个新的 lib 目录并向其中添加一个 prisma.ts 文件。此文件将用于创建和导出你的 Prisma 客户端实例。

¥Inside of your app directory, create a new lib directory and add a prisma.ts file to it. This file will be used to create and export your Prisma Client instance.

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

¥Set up the Prisma client like this:

app/lib/prisma.ts
import { PrismaClient } from "../generated/prisma/client.js";
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.

你将在下一部分中使用此客户端运行你的第一个查询。

¥You'll use this client in the next section to run your first queries.

3.2.使用 Prisma 查询数据库

¥3.2. Query your database with Prisma

现在你已经拥有一个初始化的 Prisma 客户端、一个数据库连接以及一些初始数据,你可以开始使用 Prisma ORM 查询数据。

¥Now that you have an initialized Prisma Client, a connection to your database, and some initial data, you can start querying your data with Prisma ORM.

在此示例中,你将使应用的 "home" 页面显示所有用户。

¥In this example, you'll be making the "home" page of your application display all of your users.

打开 app/routes/home.tsx 文件并将现有代码替换为以下内容:

¥Open the app/routes/home.tsx file and replace the existing code with the following:

app/routes/home.tsx
import type { Route } from "./+types/home";

export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}

export default function Home({ loaderData }: Route.ComponentProps) {
return (
<div className="min-h-screen flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)]">
Superblog
</h1>
<ol className="list-decimal list-inside font-[family-name:var(--font-geist-sans)]">
<li className="mb-2">Alice</li>
<li>Bob</li>
</ol>
</div>
);
}
注意

如果你在第一行 import type { Route } from "./+types/home"; 处看到错误,请确保运行 npm run dev,以便 React Router 生成所需的类型。

¥If you see an error on the first line, import type { Route } from "./+types/home";, make sure you run npm run dev so React Router generates needed types.

这将为你提供一个包含标题和用户列表的基本页面。但是,用户列表是静态的。更新页面,从数据库获取用户并使其动态化。

¥This gives you a basic page with a title and a list of users. However, the list of users is static. Update the page to fetch the users from your database and make it dynamic.

app/routes/home.tsx
import type { Route } from "./+types/home";
import prisma from '~/lib/prisma'

export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}

export async function loader() {
const users = await prisma.user.findMany();
return { users };
}

export default function Home({ loaderData }: Route.ComponentProps) {
const { users } = loaderData;
return (
<div className="min-h-screen flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)]">
Superblog
</h1>
<ol className="list-decimal list-inside font-[family-name:var(--font-geist-sans)]">
{users.map((user) => (
<li key={user.id} className="mb-2">
{user.name}
</li>
))}
</ol>
</div>
);
}

你现在正在导入客户端,使用 React Router 加载器 查询所有用户的 User 模型,然后将其显示在列表中。

¥You are now importing your client, using a React Router loader to query the User model for all users, and then displaying them in a list.

现在,你的主页是动态的,并将显示数据库中的用户。

¥Now your home page is dynamic and will display the users from your database.

3.4 更新数据(可选)

¥3.4 Update your data (optional)

如果你想查看数据更新时会发生什么,你可以:

¥If you want to see what happens when data is updated, you could:

  • 通过你选择的 SQL 浏览器更新你的 User

    ¥update your User table via an SQL browser of your choice

  • 更改你的 seed.ts 文件以添加更多用户

    ¥change your seed.ts file to add more users

  • 将对 prisma.user.findMany 的调用更改为重新排序用户、筛选用户或类似操作。

    ¥change the call to prisma.user.findMany to re-order the users, filter the users, or similar.

只需重新加载页面即可看到更改。

¥Just reload the page and you'll see the changes.

4. 添加新的帖子列表页面

¥ Add a new Posts list page

你的主页已正常运行,但你需要添加一个显示所有帖子的新页面。

¥You have your home page working, but you should add a new page that displays all of your posts.

首先,在 app/routes 目录下创建一个新的 posts 目录并添加一个 home.tsx 文件:

¥First, create a new posts directory under the app/routes directory and add a home.tsx file:

mkdir -p app/routes/posts && touch app/routes/posts/home.tsx

其次,将以下代码添加到 app/routes/posts/home.tsx 文件:

¥Second, add the following code to the app/routes/posts/home.tsx file:

app/routes/posts/home.tsx
import type { Route } from "./+types/home";
import prisma from "~/lib/prisma";

export default function Home() {
return (
<div className="min-h-screen flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)]">
Posts
</h1>
<ul className="font-[family-name:var(--font-geist-sans)] max-w-2xl space-y-4">
<li>My first post</li>
</ul>
</div>
);
}

其次,更新 app/routes.ts 文件,以便当你访问 /posts 路由时,显示 posts/home.tsx 页面:

¥Second, update the app/routes.ts file so when you visit the /posts route, the posts/home.tsx page is shown:

app/routes.ts
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
index("routes/home.tsx"),
route("posts", "routes/posts/home.tsx"),
] satisfies RouteConfig;

现在 localhost:5173/posts 可以加载,但内容是静态的。将其更新为动态,类似于主页:

¥Now localhost:5173/posts will load, but the content is static. Update it to be dynamic, similarly to the home page:

app/routes/posts/home.tsx
import type { Route } from "./+types/home";
import prisma from "~/lib/prisma";

export async function loader() {
const posts = await prisma.post.findMany({
include: {
author: true,
},
});
return { posts };
}

export default function Posts({ loaderData }: Route.ComponentProps) {
const { posts } = loaderData;
return (
<div className="min-h-screen flex flex-col items-center justify-center -mt-16">
<h1 className="text-4xl font-bold mb-8 font-[family-name:var(--font-geist-sans)]">
Posts
</h1>
<ul className="font-[family-name:var(--font-geist-sans)] max-w-2xl space-y-4">
{posts.map((post) => (
<li key={post.id}>
<span className="font-semibold">{post.title}</span>
<span className="text-sm text-gray-600 ml-2">
by {post.author.name}
</span>
</li>
))}
</ul>
</div>
);
}

这与主页类似,但它显示的不是用户,而是帖子。你还可以看到,你在 Prisma 客户端查询中使用了 include 来获取每篇文章的作者,以便显示作者的名称。

¥This works similarly to the home page, but instead of displaying users, it displays posts. You can also see that you've used include in your Prisma Client query to fetch the author of each post so you can display the author's name.

此 "列表视图" 是 Web 应用中最常见的模式之一。你将在应用中添加另外两个页面,这两个页面通常也需要用到:一个 "详细视图" 和一个 "创建视图"。

¥This "list view" is one of the most common patterns in web applications. You're going to add two more pages to your application which you'll also commonly need: a "detail view" and a "create view".

5. 添加新的帖子详情页面

¥ Add a new Posts detail page

为了补充帖子列表页面,你将添加一个帖子详情页面。

¥To complement the Posts list page, you'll add a Posts detail page.

routes/posts 目录中,创建一个新的 post.tsx 文件。

¥In the routes/posts directory, create a new post.tsx file.

touch app/routes/posts/post.tsx

此页面将显示单个帖子的标题、内容和作者。与其他页面一样,将以下代码添加到 app/routes/posts/post.tsx 文件:

¥This page will display a single post's title, content, and author. Just like your other pages, add the following code to the app/routes/posts/post.tsx file:

app/routes/posts/post.tsx
import type { Route } from "./+types/post";
import prisma from "~/lib/prisma";

export default function Post({ loaderData }: Route.ComponentProps) {
return (
<div className="min-h-screen flex flex-col items-center justify-center -mt-16">
<article className="max-w-2xl space-y-4 font-[family-name:var(--font-geist-sans)]">
<h1 className="text-4xl font-bold mb-8">My first post</h1>
<p className="text-gray-600 text-center">by Anonymous</p>
<div className="prose prose-gray mt-8">
No content available.
</div>
</article>
</div>
);
}

然后为此页面添加新路由:

¥And then add a new route for this page:

app/routes.ts
export default [
index("routes/home.tsx"),
route("posts", "routes/posts/home.tsx"),
route("posts/:postId", "routes/posts/post.tsx"),
] satisfies RouteConfig;

与以前一样,此页面是静态的。根据传递给页面的 params 参数,将其更新为动态:

¥As before, this page is static. Update it to be dynamic based on the params passed to the page:

app/routes/posts/post.tsx
import { data } from "react-router";
import type { Route } from "./+types/post";
import prisma from "~/lib/prisma";

export async function loader({ params }: Route.LoaderArgs) {
const { postId } = params;
const post = await prisma.post.findUnique({
where: { id: parseInt(postId) },
include: {
author: true,
},
});

if (!post) {
throw data("Post Not Found", { status: 404 });
}
return { post };
}

export default function Post({ loaderData }: Route.ComponentProps) {
const { post } = loaderData;
return (
<div className="min-h-screen flex flex-col items-center justify-center -mt-16">
<article className="max-w-2xl space-y-4 font-[family-name:var(--font-geist-sans)]">
<h1 className="text-4xl font-bold mb-8">{post.title}</h1>
<p className="text-gray-600 text-center">by {post.author.name}</p>
<div className="prose prose-gray mt-8">
{post.content || "No content available."}
</div>
</article>
</div>
);
}

这里有很多变化,请逐一分析:

¥There's a lot of changes here, so break it down:

  • 你将使用 Prisma 客户端通过 id 获取帖子,id 是从 params 对象获取的。

    ¥You're using Prisma Client to fetch the post by its id, which you get from the params object.

  • 如果帖子不存在(可能是已被删除或你输入了错误的 ID),则抛出错误并显示 404 页面。

    ¥In case the post doesn't exist (maybe it was deleted or maybe you typed a wrong ID), you throw an error to display a 404 page.

  • 然后,你将显示帖子的标题、内容和作者。如果帖子没有内容,则会显示一条占位符消息。

    ¥You then display the post's title, content, and author. If the post doesn't have content, you display a placeholder message.

这不是最漂亮的页面,但它是一个好的开始。请尝试导航至 localhost:5173/posts/1localhost:5173/posts/2。你还可以通过导航到 localhost:5173/posts/999 来测试 404 页面。

¥It's not the prettiest page, but it's a good start. Try it out by navigating to localhost:5173/posts/1 and localhost:5173/posts/2. You can also test the 404 page by navigating to localhost:5173/posts/999.

6. 添加新的帖子创建页面

¥ Add a new Posts create page

要完善你的应用,你需要添加一个用于发布帖子的 "create" 页面。这将允许你撰写自己的帖子并将其保存到数据库中。

¥To round out your application, you'll add a "create" page for posts. This will allow you to write your own posts and save them to the database.

与其他页面一样,你将从静态页面开始,然后将其更新为动态页面。

¥As with the other pages, you'll start with a static page and then update it to be dynamic.

touch app/routes/posts/new.tsx

现在,将以下代码添加到 app/routes/posts/new.tsx 文件中:

¥Now, add the following code to the app/routes/posts/new.tsx file:

app/routes/posts/new.tsx
import type { Route } from "./+types/new";
import { Form } from "react-router";

export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const title = formData.get("title") as string;
const content = formData.get("content") as string;
}

export default function NewPost() {
return (
<div className="max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">Create New Post</h1>
<Form method="post" className="space-y-6">
<div>
<label htmlFor="title" className="block text-lg mb-2">
Title
</label>
<input
type="text"
id="title"
name="title"
placeholder="Enter your post title"
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label htmlFor="content" className="block text-lg mb-2">
Content
</label>
<textarea
id="content"
name="content"
placeholder="Write your post content here..."
rows={6}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600"
>
Create Post
</button>
</Form>
</div>
);
}

你目前无法在应用中打开 posts/new 页面。为此,你需要再次将其添加到 routes.tsx

¥You can't open the posts/new page in your app yet. To do that, you need to add it to routes.tsx again:

app/routes.ts
export default [
index("routes/home.tsx"),
route("posts", "routes/posts/home.tsx"),
route("posts/:postId", "routes/posts/post.tsx"),
route("posts/new", "routes/posts/new.tsx"),
] satisfies RouteConfig;

现在你可以在新的 URL 上查看表单。它看起来不错,但目前还没有任何作用。更新 action 参数,将帖子保存到数据库:

¥Now you can view the form at the new URL. It looks good, but it doesn't do anything yet. Update the action to save the post to the database:

app/routes/posts/new.tsx
import type { Route } from "./+types/new";
import { Form, redirect } from "react-router";
import prisma from "~/lib/prisma";

export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const title = formData.get("title") as string;
const content = formData.get("content") as string;

try {
await prisma.post.create({
data: {
title,
content,
authorId: 1,
},
});
} catch (error) {
console.error(error);
return Response.json({ error: "Failed to create post" }, { status: 500 });
}

return redirect("/posts");
}

export default function NewPost() {
return (
<div className="max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">Create New Post</h1>
<Form method="post" className="space-y-6">
<div>
<label htmlFor="title" className="block text-lg mb-2">
Title
</label>
<input
type="text"
id="title"
name="title"
placeholder="Enter your post title"
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label htmlFor="content" className="block text-lg mb-2">
Content
</label>
<textarea
id="content"
name="content"
placeholder="Write your post content here..."
rows={6}
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
type="submit"
className="w-full bg-blue-500 text-white py-3 rounded-lg hover:bg-blue-600"
>
Create Post
</button>
</Form>
</div>
);
}

此页面现已具备功能表单!提交表单后,它将在数据库中创建新帖子,并将你重定向到帖子列表页面。

¥This page now has a functional form! When you submit the form, it will create a new post in the database and redirect you to the posts list page.

请尝试导航至 localhost:5173/posts/new 并提交表单。

¥Try it out by navigating to localhost:5173/posts/new and submitting the form.

7. 下一步

¥ Next steps

现在你已经拥有一个使用 Prisma ORM 运行的 React Router 应用,你可以通过以下几种方式扩展和改进你的应用:

¥Now that you have a working React Router application with Prisma ORM, here are some ways you can expand and improve your application:

  • 添加身份验证以保护你的路由

    ¥Add authentication to protect your routes

  • 添加编辑和删除帖子的功能

    ¥Add the ability to edit and delete posts

  • 在帖子中添加评论

    ¥Add comments to posts

  • 使用 Prisma 工作室 进行可视化数据库管理

    ¥Use Prisma Studio for visual database management

更多信息和更新:

¥For more information and updates: