如何在 TanStack Start 中使用 Prisma ORM
介绍
¥Introduction
Prisma ORM 简化了数据库交互,TanStack Start 为构建现代 React 应用提供了一个强大的框架。它们与 Prisma Postgres 一起,提供无缝的全栈开发体验,具有类型安全的查询和高效的数据管理。
¥Prisma ORM simplifies database interactions, and TanStack Start offers a robust framework for building modern React applications. Together with Prisma Postgres, they provide a seamless full-stack development experience with type-safe queries and efficient data management.
本指南将指导你从头开始在 TanStack Start 项目中将 Prisma ORM 与 Prisma Postgres 数据库集成。
¥This guide will walk you through integrating Prisma ORM with a Prisma Postgres database in a TanStack Start project from scratch.
先决条件
¥Prerequisites
1. 设置你的项目
¥ Set up your project
首先,创建一个新的 TanStack Start 项目。
¥To begin, create a new TanStack Start project.
在本指南中,我们使用的设置说明与你在 TanStart 启动文档 中找到的相同。
¥For the purpose of this guide, we're using the same setup instructions that you can find in the TanStart Start docs.
在你想要创建项目的目录中,运行以下命令:
¥In the directory where you'd like to create your project, run the following commands:
mkdir tanstack-start-prisma
cd tanstack-start-prisma
npm init -y
这将创建一个名为 tanstack-start-prisma
的新文件夹,导航到该文件夹,并初始化一个新的 Node.js 项目。
¥This will create a new folder called tanstack-start-prisma
, navigate into it, and initialize a new Node.js project.
在 IDE 中打开目录并创建一个 tsconfig.json
文件,配置如下:
¥Open the directory in your IDE and create a tsconfig.json
file with the following configuration:
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true
}
}
我们还需要一个 .gitignore
文件,现在就开始设置:
¥We also need a .gitignore
file, so let's set that up now:
node_modules
.env
app/generated
接下来,安装 TanStack Router 和 Vinxi,因为 TanStack Start 目前需要它们:
¥Next, install TanStack Router and Vinxi, as TanStack Start currently requires them:
npm install @tanstack/react-start @tanstack/react-router vinxi
我们还需要 React、Vite React 插件和 TypeScript:
¥We also need React, the Vite React plugin, and 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:
¥Update your package.json
to use Vinxi's CLI. Add "type": "module"
and modify the scripts to use Vinxi's CLI:
{
"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
文件:
¥Then, create and configure TanStack Start's app.config.ts
file:
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 个文件:
¥For TanStack Start to function, we need 5 files in ~/app/
:
-
router.tsx
(路由配置)¥
router.tsx
(The router configuration) -
ssr.tsx
(服务器入口点)¥
ssr.tsx
(The server entry point) -
client.tsx
(客户端入口点)¥
client.tsx
(The client entry point) -
routes/__root.tsx
(应用的根目录)¥
routes/__root.tsx
(The root of the app) -
routes/index.tsx
(主页)¥
routes/index.tsx
(The home page)
你可以使用以下命令创建它们:
¥You can create them with these commands:
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
使用路由定义和设置配置应用的主路由:
¥router.tsx
configures the application's main router with route definitions and settings:
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 时生成。
¥You should be seeing an error about routeTree.gen.ts
not existing. This is expected. It will be generated when you run TanStack Start for the first time.
ssr.tsx
让我们知道当用户点击指定路由时需要执行哪些路由和加载器:
¥ssr.tsx
allows us to know what routes and loaders we need to execute when the user hits a given route:
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
初始化客户端逻辑以处理浏览器中的路由:
¥client.tsx
initializes the client-side logic to handle routes in the browser:
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 布局:
¥routes/__root.tsx
defines the root route and global HTML layout for the entire application:
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
是应用的主页:
¥routes/index.tsx
is the home page of the application:
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({
component: Home,
});
function Home() {
return (
<div>
<h1>Posts</h1>
</div>
);
}
现在,运行:
¥Now, run:
npm run dev
这将生成 routeTree.gen.ts
文件并解决任何路由错误。
¥This will generate the routeTree.gen.ts
file and resolve any routing errors.
你的文件树应如下所示(不包含 node_modules
):
¥Your file tree should look like this (without 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
¥ Install and Configure Prisma
2.1.安装依赖
¥2.1. Install dependencies
要开始使用 Prisma,你需要安装一些依赖:
¥To get started with Prisma, you'll need to install a few dependencies:
- Prisma Postgres (recommended)
- Other databases
npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @prisma/client
npm install prisma tsx --save-dev
npm install @prisma/client
安装完成后,请在你的项目中初始化 Prisma:
¥Once installed, initialize Prisma in your project:
npx prisma init --db --output ../app/generated/prisma
在设置 Prisma Postgres 数据库时,你需要回答几个问题。选择离你最近的区域,并为数据库选择一个容易记住的名称,例如 "我的 __________ 项目"
¥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 __________ Project"
这将造成:
¥This will create:
-
一个包含
schema.prisma
文件的prisma
目录。¥A
prisma
directory with aschema.prisma
file. -
一个 Prisma Postgres 数据库。
¥A Prisma Postgres database.
-
一个位于项目根目录、包含
DATABASE_URL
文件的.env
文件。¥A
.env
file containing theDATABASE_URL
at the project root. -
用于生成的 Prisma 客户端的
output
目录,用作app/generated/prisma
。¥An
output
directory for the generated Prisma Client asapp/generated/prisma
.
2.2.定义 Prisma Schema
¥2.2. Define your Prisma Schema
在 schema.prisma
中,为我们的帖子创建一个模型,并将生成器更改为使用 prisma-client
提供程序:
¥In schema.prisma
, create a model for our posts and change the generator to use the prisma-client
provider:
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
,它们之间存在一对多关系。
¥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
让我们添加一些种子数据,以便用示例用户和帖子填充数据库。
¥Let's 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:
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 如何运行此脚本:
¥Now, tell Prisma how to run this script by updating your package.json
:
"prisma": {
"seed": "tsx prisma/seed.ts"
}
运行种子脚本:
¥Run the seed script:
npx prisma db seed
打开 Prisma Studio 检查你的数据:
¥And open Prisma Studio to inspect your data:
npx prisma studio
3. 将 Prisma 集成到 TanStack Start
¥ Integrate Prisma into TanStack Start
3.1 创建 Prisma 客户端
¥3.1 Create a Prisma Client
无需在每个文件中创建新的 Prisma 客户端实例,而是在共享文件中创建一个全局使用的实例。
¥Instead of creating a new Prisma Client instance in each file, create a single instance in a shared file to be used globally.
在其中创建一个 /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.
按如下方式设置 Prisma 客户端:
¥Set up the Prisma client like this:
- Prisma Postgres (recommended)
- Other databases
import { PrismaClient } from "../generated/prisma/client.js";
import { withAccelerate } from "@prisma/extension-accelerate";
const prisma = new PrismaClient().$extends(withAccelerate());
export default prisma;
import { PrismaClient } from "../generated/prisma/client.js";
const prisma = new PrismaClient();
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.
3.2 加载时获取用户和帖子
¥3.2 Fetch users and posts on load
首先,导入必要的模块。然后,使用 createServerFn
函数创建一个服务器函数。此函数将使用 .findMany()
方法从数据库中获取用户。使用 include
选项获取相关文章:
¥First, import the necessary modules. Then, create a server function using the createServerFn
function. This function will fetch the users from the database using the .findMany()
method. Use the include
option to fetch the related posts:
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
函数中使用加载器函数在负载下运行。使用以下代码在加载时获取用户及其帖子:
¥TanStack Start allows functions to run on load with loader functions in the createFileRoute
function. Fetch the users and their posts on load with this code:
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()
将加载器的响应存储在主组件中:
¥Store the response from the loader in the main component using Route.useLoaderData()
:
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 显示用户和帖子
¥3.3 Display the users and posts
接下来,你将更新主页以显示从数据库检索到的用户和帖子。
¥Next, you'll update the home page to display the users and posts retrieved from your database.
映射 users
并将它们与 posts
一起显示在列表中:
¥Map over the users
and display them in a list along with their posts
:
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>
);
}
此设置将在你的页面上显示直接从数据库获取的帖子。
¥This setup will display the posts on your page, fetched directly from your database.
下一步
¥Next steps
你已成功将 Prisma ORM 与 TanStack Start 集成,从而创建了一个无缝的全栈应用。以下是一些关于你接下来可以做什么的建议:
¥You've successfully integrated Prisma ORM with TanStack Start, creating a seamless full-stack application. Here are a few suggestions for what you can do next:
-
扩展你的 Prisma 模型以处理更复杂的数据关系。
¥Expand your Prisma models to handle more complex data relationships.
-
实现额外的 CRUD 操作以增强应用的功能。
¥Implement additional CRUD operations to enhance your application's functionality.
-
探索 Prisma 和 TanStack 的更多功能,加深你的理解。
¥Explore more features of Prisma and TanStack Start to deepen your understanding.
-
查看 Prisma Postgres,了解如何扩展你的应用。
¥Check out Prisma Postgres to see how you can scale your application.
更多信息
¥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:
- Follow us on X for announcements, live events and useful tips.
- Join our Discord to ask questions, talk to the community, and get active support through conversations.
- Subscribe on YouTube for tutorials, demos, and streams.
- Engage on GitHub by starring the repository, reporting issues, or contributing to an issue.