跳至主要内容

如何在 TanStack Start 中使用 Prisma ORM

10 分钟

简介

Prisma ORM 简化了数据库交互,而 TanStack Start 为构建现代 React 应用程序提供了强大的框架。结合 Prisma Postgres,它们提供无缝的全栈开发体验,支持类型安全的查询和高效的数据管理。

本指南将引导您从头开始,在 TanStack Start 项目中集成 Prisma ORM 和 Prisma Postgres 数据库。

先决条件

1. 设置您的项目

首先,创建一个新的 TanStack Start 项目。

注意

为了本指南的目的,我们使用的设置说明与您在 TanStack Start 文档 中找到的相同。

在您希望创建项目的目录中,运行以下命令:

mkdir tanstack-start-prisma
cd tanstack-start-prisma
npm init -y

这将在当前目录中创建一个名为 tanstack-start-prisma 的新文件夹,然后进入该文件夹并初始化一个新的 Node.js 项目。

在您的 IDE 中打开该目录,并创建一个 tsconfig.json 文件,配置如下:

tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true
}
}

我们还需要一个 .gitignore 文件,现在就来设置它:

.gitignore
node_modules
.env
app/generated

接下来,安装 TanStack Router 和 Vinxi,因为 TanStack Start 目前需要它们:

npm install @tanstack/react-start @tanstack/react-router vinxi

我们还需要 React、Vite React 插件和 TypeScript:

npm install react react-dom
npm install --save-dev @vitejs/plugin-react vite-tsconfig-paths
npm install --save-dev typescript @types/react @types/react-dom

更新您的 package.json 以使用 Vinxi 的 CLI。添加 "type": "module" 并修改脚本以使用 Vinxi 的 CLI:

package.json
{
"name": "tanstack-start-prisma",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@tanstack/react-router": "^1.119.0",
"@tanstack/react-start": "^1.119.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"vinxi": "^0.5.6"
},
"devDependencies": {
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@vitejs/plugin-react": "^4.4.1",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "^5.1.4"
}
}

然后,创建并配置 TanStack Start 的 app.config.ts 文件:

app.config.ts
import { defineConfig } from '@tanstack/react-start/config'
import tsConfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
vite: {
plugins: [
tsConfigPaths({
projects: ['./tsconfig.json'],
}),
],
},
})

为了使 TanStack Start 正常运行,我们还需要在 ~/app/ 中创建 5 个文件:

  • router.tsx(路由器配置)
  • ssr.tsx(服务器入口点)
  • client.tsx(客户端入口点)
  • routes/__root.tsx(应用程序的根)
  • routes/index.tsx(主页)

您可以使用以下命令创建它们:

mkdir app
touch app/router.tsx
touch app/ssr.tsx
touch app/client.tsx
mkdir app/routes
touch app/routes/__root.tsx
touch app/routes/index.tsx

router.tsx 配置应用程序的主路由器,包括路由定义和设置。

app/router.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function createRouter() {
const router = createTanStackRouter({
routeTree,
scrollRestoration: true,
})

return router
}

declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof createRouter>
}
}
注意

您应该会看到一个关于 routeTree.gen.ts 不存在的错误。这是预期的。它将在您第一次运行 TanStack Start 时生成。

ssr.tsx 允许我们知道当用户访问给定路由时需要执行哪些路由和加载器。

app/ssr.tsx
import {
createStartHandler,
defaultStreamHandler,
} from '@tanstack/react-start/server'
import { getRouterManifest } from '@tanstack/react-start/router-manifest'

import { createRouter } from './router'

export default createStartHandler({
createRouter,
getRouterManifest,
})(defaultStreamHandler)

client.tsx 初始化客户端逻辑以处理浏览器中的路由。

app/client.tsx
import { hydrateRoot } from "react-dom/client";
import { StartClient } from "@tanstack/react-start/client";
import { createRouter } from "./router";

const router = createRouter();

hydrateRoot(document, <StartClient router={router} />);

routes/__root.tsx 定义了整个应用程序的根路由和全局 HTML 布局。

app/routes/__root.tsx
import type { ReactNode } from "react";
import {
Outlet,
createRootRoute,
HeadContent,
Scripts,
} from "@tanstack/react-router";

export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
{
title: "Prisma TanStack Start Demo",
},
],
}),
component: RootComponent,
});

function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
);
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}

routes/index.tsx 是应用程序的主页。

app/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
component: Home,
});

function Home() {
return (
<div>
<h1>Posts</h1>
</div>
);
}

现在,运行:

npm run dev

这将生成 routeTree.gen.ts 文件并解决所有路由错误。

您的文件树应如下所示(不包含 node_modules):

.
├── app
│ ├── client.tsx
│ ├── routeTree.gen.ts
│ ├── router.tsx
│ ├── routes
│ │ ├── __root.tsx
│ │ └── index.tsx
│ └── ssr.tsx
├── app.config.ts
├── package-lock.json
├── package.json
└── tsconfig.json

2. 安装和配置 Prisma

2.1. 安装依赖项

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

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

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

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

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

这将创建:

  • 一个包含 schema.prisma 文件的 prisma 目录。
  • 一个 Prisma Postgres 数据库。
  • 项目根目录下的一个包含 DATABASE_URL.env 文件。
  • 一个用于生成 Prisma Client 的 output 目录,路径为 app/generated/prisma

2.2. 定义您的 Prisma Schema

schema.prisma 中,为我们的文章创建一个模型,并将生成器更改为使用 prisma-client 提供者:

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,它们之间存在一对多关系。

2.3. 配置 Prisma Client 生成器

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

npx prisma migrate dev --name init

2.4. 填充数据库

让我们添加一些种子数据,用示例用户和文章来填充数据库。

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

prisma/seed.ts
import { PrismaClient, Prisma } from "../src/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 如何运行此脚本:

package.json
"prisma": {
"seed": "tsx prisma/seed.ts"
}

运行种子脚本:

npx prisma db seed

并打开 Prisma Studio 来检查您的数据:

npx prisma studio

3. 将 Prisma 集成到 TanStack Start

3.1 创建一个 Prisma Client

与其在每个文件中创建新的 Prisma Client 实例,不如在共享文件中创建一个单例实例以供全局使用。

创建一个 /lib 目录并在其中创建一个 prisma.ts 文件。该文件将用于创建和导出您的 Prisma Client 实例。

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

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

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

export default prisma;
警告

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

如果您选择不使用连接池,请避免在长时间运行的环境中全局实例化 PrismaClient。相反,应为每个请求创建和销毁客户端,以防止耗尽数据库连接。

3.2 在加载时获取用户和文章

首先,导入必要的模块。然后,使用 createServerFn 函数创建一个服务器函数。该函数将使用 .findMany() 方法从数据库中获取用户。使用 include 选项来获取相关的文章。

app/routes/index.tsx
import { prisma } from "../lib/prisma";
import { createServerFn } from "@tanstack/react-start";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
component: Home,
});

const getUsers = createServerFn({ method: "GET" }).handler(async () => {
return prisma.user.findMany({
include: {
posts: true,
},
});
});

function Home() {
return (
<div>
<h1>Posts</h1>
</div>
);
}

TanStack Start 允许函数在加载时通过 createFileRoute 函数中的加载器函数运行。使用此代码在加载时获取用户及其文章:

app/routes/index.tsx
import { prisma } from "../lib/prisma";
import { createServerFn } from "@tanstack/react-start";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
component: Home,
loader: () => {
return getUsers();
},
});

const getUsers = createServerFn({ method: "GET" }).handler(async () => {
return prisma.user.findMany({
include: {
posts: true,
},
});
});

function Home() {
return (
<div>
<h1>Posts</h1>
</div>
);
}

使用 Route.useLoaderData() 将加载器的响应存储在主组件中:

app/routes/index.tsx
import { prisma } from "../lib/prisma";
import { createServerFn } from "@tanstack/react-start";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
component: Home,
loader: () => {
return getUsers();
},
});

const getUsers = createServerFn({ method: "GET" }).handler(async () => {
return prisma.user.findMany({
include: {
posts: true,
},
});
});

function Home() {
const users = Route.useLoaderData();

return (
<div>
<h1>Posts</h1>
</div>
);
}

3.3 显示用户和文章

接下来,您将更新主页以显示从数据库中检索到的用户和文章。

遍历 users 并将其与 posts 一起显示在列表中:

app/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import prisma from "../../lib/prisma";

export const Route = createFileRoute("/")({
component: Home,
loader: () => {
return getUsers();
},
});

const getUsers = createServerFn({ method: "GET" }).handler(async () => {
return prisma.user.findMany({
include: {
posts: true,
},
});
});

function Home() {
const users = Route.useLoaderData();

return (
<div>
<h1>Posts</h1>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}
<ul>
{user.posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</li>
))}
</ul>
</div>
);
}

此设置将在您的页面上显示直接从数据库中获取的文章。

下一步

您已成功将 Prisma ORM 与 TanStack Start 集成,创建了一个无缝的全栈应用程序。以下是您可以接下来做的一些建议:

  • 扩展您的 Prisma 模型以处理更复杂的数据关系。
  • 实现额外的 CRUD 操作以增强应用程序的功能。
  • 探索 Prisma 和 TanStack Start 的更多功能,以加深您的理解。
  • 查看 Prisma Postgres,了解如何扩展您的应用程序。

更多信息


与 Prisma 保持联系

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

我们衷心感谢您的参与,并期待您成为我们社区的一员!

© . All rights reserved.