Skip to main content

如何在 TanStack Start 中使用 Prisma ORM

10 min

介绍

¥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:

tsconfig.json
{
"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:

.gitignore
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:

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 文件:

¥Then, create and configure TanStack Start's app.config.ts file:

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 个文件:

¥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:

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 时生成。

¥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:

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 初始化客户端逻辑以处理浏览器中的路由:

¥client.tsx initializes the client-side logic to handle routes in the browser:

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 布局:

¥routes/__root.tsx defines the root route and global HTML layout for the entire application:

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 是应用的主页:

¥routes/index.tsx is the home page of the application:

app/routes/index.tsx
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:

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

¥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 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

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

¥In schema.prisma, create a model for our posts 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

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

¥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:

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 如何运行此脚本:

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

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:

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 加速)来高效管理数据库连接。

¥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:

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 函数中使用加载器函数在负载下运行。使用以下代码在加载时获取用户及其帖子:

¥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:

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() 将加载器的响应存储在主组件中:

¥Store the response from the loader in the main component using 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 显示用户和帖子

¥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:

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>
);
}

此设置将在你的页面上显示直接从数据库获取的帖子。

¥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:

We genuinely value your involvement and look forward to having you as part of our community!