如何在 React Router 7 中使用 Prisma ORM
簡介
本指南將向您展示如何在 React Router 7 中使用 Prisma ORM,React Router 7 是一個多策略路由器,既可以提供極簡的宣告式路由,也可以提供全棧框架的完整功能。
您將學習如何使用 React Router 7 設定 Prisma ORM 和 Prisma Postgres 並處理遷移。您可以在 GitHub 上找到一個可部署的示例。
先決條件
1. 設定您的專案
在您要建立專案的目錄中,執行 create-react-router 建立一個新的 React Router 應用程式,您將在本指南中使用它。
npx create-react-router@latest react-router-7-prisma
您將被提示選擇以下選項,請選擇 Yes(是)
- 初始化一個新的 git 倉庫?
Yes - 使用 npm 安裝依賴?
Yes
現在,導航到專案目錄
cd react-router-7-prisma
2. 安裝和配置 Prisma
2.1. 安裝依賴項
要開始使用 Prisma,您需要安裝一些依賴項
- Prisma Postgres(推薦)
- 其他資料庫
npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @prisma/client
npm install prisma tsx --save-dev
npm install @prisma/client
安裝完成後,在您的專案中初始化 Prisma
npx prisma init --db --output ../app/generated/prisma
在設定 Prisma Postgres 資料庫時,您需要回答幾個問題。選擇離您位置最近的區域,併為您的資料庫選擇一個易於記憶的名稱,例如“My React Router 7 Project”
這將建立
- 一個包含
schema.prisma檔案的prisma目錄。 - 一個 Prisma Postgres 資料庫。
- 專案根目錄中包含
DATABASE_URL的.env檔案。 - 一個用於生成的 Prisma Client 的
output目錄,路徑為app/generated/prisma。
2.2. 定義您的 Prisma Schema
在 prisma/schema.prisma 檔案中,新增以下模型並更改生成器以使用 prisma-client 提供程式
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])
}
這將建立兩個模型:User 和 Post,它們之間存在一對多關係。
2.3. 配置 Prisma Client 生成器
現在,執行以下命令來建立資料庫表並生成 Prisma Client
npx prisma migrate dev --name init
2.4. 填充資料庫
新增一些填充資料以填充資料庫中的示例使用者和帖子。
在 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 如何執行此指令碼
{
"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"
}
}
執行填充指令碼
npx prisma db seed
並開啟 Prisma Studio 檢查您的資料
npx prisma studio
3. 將 Prisma 整合到 React Router 7 中
3.1. 建立一個 Prisma Client
在您的 app 目錄內,建立一個新的 lib 目錄,並在其中新增一個 prisma.ts 檔案。此檔案將用於建立和匯出您的 Prisma Client 例項。
像這樣設定 Prisma 客戶端
- Prisma Postgres(推薦)
- 其他資料庫
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
import { PrismaClient } from "../generated/prisma/client.js";
const globalForPrisma = global as unknown as {
prisma: PrismaClient
}
const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
export default prisma
我們建議使用連線池(例如 Prisma Accelerate)來高效管理資料庫連線。
如果您選擇不使用連線池,請**避免**在長期執行的環境中全域性例項化 PrismaClient。相反,請按需建立和銷燬客戶端,以防止耗盡您的資料庫連線。
您將在下一節中使用此客戶端執行您的第一個查詢。
3.2. 使用 Prisma 查詢您的資料庫
現在您已經初始化了 Prisma Client,連線到了資料庫,並且擁有了一些初始資料,您可以開始使用 Prisma ORM 查詢您的資料了。
在此示例中,您將使應用程式的“主頁”顯示所有使用者。
開啟 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 生成所需的型別。
這為您提供了一個帶有標題和使用者列表的基本頁面。但是,使用者列表是靜態的。更新頁面以從資料庫中獲取使用者並使其動態化。
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 模型以獲取所有使用者,然後將它們顯示在一個列表中。
現在您的主頁是動態的,並將顯示來自您資料庫的使用者。
3.4 更新您的資料(可選)
如果您想檢視資料更新時會發生什麼,您可以
- 透過您選擇的 SQL 瀏覽器更新您的
User表 - 更改您的
seed.ts檔案以新增更多使用者 - 更改
prisma.user.findMany的呼叫以重新排序使用者、過濾使用者等。
只需重新載入頁面,您就會看到更改。
4. 新增一個新的帖子列表頁面
您的主頁已經可以正常工作,但是您應該新增一個新頁面來顯示您的所有帖子。
首先,在 app/routes 目錄下建立一個新的 posts 目錄,並新增一個 home.tsx 檔案
mkdir -p app/routes/posts && touch app/routes/posts/home.tsx
其次,將以下程式碼新增到 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 頁面
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 將載入,但內容是靜態的。更新它以使其動態化,類似於主頁
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 Client 查詢中使用了 include 來獲取每個帖子的作者,以便您可以顯示作者的姓名。
這種“列表檢視”是 Web 應用程式中最常見的模式之一。您將向您的應用程式新增另外兩個您也通常需要的頁面:“詳細檢視”和“建立檢視”。
5. 新增一個新的帖子詳情頁面
為了完善帖子列表頁面,您將新增一個帖子詳情頁面。
在 routes/posts 目錄中,建立一個新的 post.tsx 檔案。
touch app/routes/posts/post.tsx
此頁面將顯示單個帖子的標題、內容和作者。就像您的其他頁面一樣,將以下程式碼新增到 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>
);
}
然後為此頁面新增一個新路由
export default [
index("routes/home.tsx"),
route("posts", "routes/posts/home.tsx"),
route("posts/:postId", "routes/posts/post.tsx"),
] satisfies RouteConfig;
如前所述,此頁面是靜態的。更新它以根據傳遞給頁面的 params 進行動態化
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>
);
}
這裡有很多變化,讓我們分解一下
- 您正在使用 Prisma Client 透過其
id獲取帖子,該id從params物件獲取。 - 如果帖子不存在(可能是已刪除或您輸入了錯誤的 ID),您將丟擲錯誤以顯示 404 頁面。
- 然後,您顯示帖子的標題、內容和作者。如果帖子沒有內容,則顯示佔位符訊息。
它不是最漂亮的頁面,但這是一個很好的開始。嘗試導航到 localhost:5173/posts/1 和 localhost:5173/posts/2。您還可以透過導航到 localhost:5173/posts/999 來測試 404 頁面。
6. 新增新的帖子建立頁面
為了完善您的應用程式,您將為帖子新增一個“建立”頁面。這將允許您編寫自己的帖子並將其儲存到資料庫中。
與其它頁面一樣,您將從一個靜態頁面開始,然後將其更新為動態頁面。
touch app/routes/posts/new.tsx
現在,將以下程式碼新增到 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 中
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 以將帖子儲存到資料庫中
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>
);
}
這個頁面現在有一個功能齊全的表單!當您提交表單時,它將在資料庫中建立一個新帖子,並將您重定向到帖子列表頁面。
嘗試導航到 localhost:5173/posts/new 並提交表單。
7. 下一步
現在您已經擁有一個使用 Prisma ORM 的正常執行的 React Router 應用程式,以下是一些您可以擴充套件和改進應用程式的方法
- 新增身份驗證以保護您的路由
- 新增編輯和刪除帖子的功能
- 新增帖子評論
- 使用 Prisma Studio 進行視覺化資料庫管理
更多資訊和更新
- Prisma ORM 文件
- Prisma Client API 參考
- React Router 文件
- 加入我們的 Discord 社群
- 關注我們的 Twitter 和 YouTube