tRPC 快速开始 #

环境准备 #

在开始之前,请确保你的开发环境满足以下要求:

必需环境 #

text
┌─────────────────────────────────────────────────────────────┐
│                    环境要求                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Node.js:  >= 18.x                                          │
│  pnpm/npm/yarn: 最新稳定版                                  │
│  TypeScript: >= 4.7                                         │
│                                                             │
│  推荐开发工具:                                              │
│  - VS Code                                                  │
│  - TypeScript 扩展                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

检查环境 #

bash
node -v
npm -v
npx tsc -v

项目初始化 #

方式一:使用脚手架(推荐) #

bash
create-trpc-appx my-trpc-app

选择项目类型:

text
? What type of project would you like to create? 
  ❯ Next.js
    Express
    Standalone

方式二:手动安装 #

让我们从零开始手动配置一个 tRPC 项目:

bash
mkdir my-trpc-app
cd my-trpc-app
npm init -y

安装依赖 #

服务端依赖 #

bash
npm install @trpc/server zod

客户端依赖 #

bash
npm install @trpc/client @trpc/react-query @tanstack/react-query

开发依赖 #

bash
npm install -D typescript @types/node tsx

完整的 package.json #

json
{
  "name": "my-trpc-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  },
  "dependencies": {
    "@trpc/server": "^11.0.0",
    "@trpc/client": "^11.0.0",
    "@trpc/react-query": "^11.0.0",
    "@tanstack/react-query": "^5.0.0",
    "zod": "^3.22.0",
    "express": "^4.18.0",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "@types/express": "^4.17.0",
    "@types/cors": "^2.8.0",
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0",
    "tsx": "^4.0.0"
  }
}

TypeScript 配置 #

创建 tsconfig.json

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

项目结构 #

text
my-trpc-app/
├── src/
│   ├── server/
│   │   ├── index.ts          # 服务端入口
│   │   ├── trpc.ts           # tRPC 初始化
│   │   └── routers/
│   │       ├── index.ts      # 合并路由
│   │       └── user.ts       # 用户路由
│   ├── client/
│   │   ├── index.ts          # 客户端入口
│   │   └── trpc.ts           # tRPC 客户端配置
│   └── shared/
│       └── types.ts          # 共享类型
├── package.json
└── tsconfig.json

创建服务端 #

步骤 1:初始化 tRPC #

创建 src/server/trpc.ts

typescript
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';

export const t = initTRPC.create({
  transformer: superjson,
});

export const router = t.router;
export const publicProcedure = t.procedure;
text
┌─────────────────────────────────────────────────────────────┐
│                    tRPC 初始化                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  initTRPC.create() 创建 tRPC 实例                           │
│                                                             │
│  主要导出:                                                  │
│  - router: 创建路由器                                       │
│  - procedure: 创建过程(API 端点)                          │
│                                                             │
│  transformer:                                               │
│  - superjson 支持更多类型(Date、Map、Set 等)              │
│  - 可选配置                                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

步骤 2:创建路由 #

创建 src/server/routers/user.ts

typescript
import { z } from 'zod';
import { router, publicProcedure } from '../trpc';

let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
];

export const userRouter = router({
  list: publicProcedure.query(() => {
    return users;
  }),

  getById: publicProcedure
    .input(z.object({ id: z.number() }))
    .query(({ input }) => {
      return users.find((user) => user.id === input.id);
    }),

  create: publicProcedure
    .input(
      z.object({
        name: z.string().min(1),
        email: z.string().email(),
      })
    )
    .mutation(({ input }) => {
      const newUser = {
        id: users.length + 1,
        ...input,
      };
      users.push(newUser);
      return newUser;
    }),

  update: publicProcedure
    .input(
      z.object({
        id: z.number(),
        name: z.string().optional(),
        email: z.string().email().optional(),
      })
    )
    .mutation(({ input }) => {
      const index = users.findIndex((user) => user.id === input.id);
      if (index === -1) {
        throw new Error('User not found');
      }
      users[index] = { ...users[index], ...input };
      return users[index];
    }),

  delete: publicProcedure
    .input(z.object({ id: z.number() }))
    .mutation(({ input }) => {
      users = users.filter((user) => user.id !== input.id);
      return { success: true };
    }),
});

步骤 3:合并路由 #

创建 src/server/routers/index.ts

typescript
import { router } from '../trpc';
import { userRouter } from './user';

export const appRouter = router({
  user: userRouter,
});

export type AppRouter = typeof appRouter;
text
┌─────────────────────────────────────────────────────────────┐
│                    路由合并                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  appRouter 结构:                                           │
│                                                             │
│  appRouter                                                  │
│  └── user                                                   │
│      ├── list        -> user.list()                        │
│      ├── getById     -> user.getById({ id: 1 })            │
│      ├── create      -> user.create({ name, email })       │
│      ├── update      -> user.update({ id, ... })           │
│      └── delete      -> user.delete({ id })                │
│                                                             │
│  AppRouter 类型导出:                                        │
│  - 客户端需要此类型来获得类型安全                           │
│  - 确保前后端类型一致                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

步骤 4:创建 HTTP 服务 #

创建 src/server/index.ts

typescript
import express from 'express';
import cors from 'cors';
import * as trpcExpress from '@trpc/server/adapters/express';
import { appRouter } from './routers';
import { createContext } from './context';

const app = express();

app.use(cors({ origin: 'http://localhost:3000' }));

app.use(
  '/trpc',
  trpcExpress.createExpressMiddleware({
    router: appRouter,
    createContext,
  })
);

app.listen(4000, () => {
  console.log('Server running on http://localhost:4000');
});

步骤 5:创建上下文(可选) #

创建 src/server/context.ts

typescript
import * as trpcExpress from '@trpc/server/adapters/express';

interface User {
  id: string;
  name: string;
}

export interface Context {
  user: User | null;
}

export async function createContext({
  req,
}: trpcExpress.CreateExpressContextOptions): Promise<Context> {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return { user: null };
  }

  return { user: { id: '1', name: 'User' } };
}

创建客户端 #

步骤 1:配置 tRPC 客户端 #

创建 src/client/trpc.ts

typescript
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/routers';

export const trpc = createTRPCReact<AppRouter>();

步骤 2:创建客户端实例 #

创建 src/client/index.ts

typescript
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/routers';

export const client = createTRPCProxyClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:4000/trpc',
    }),
  ],
});

async function main() {
  const users = await client.user.list.query();
  console.log('Users:', users);

  const user = await client.user.getById.query({ id: 1 });
  console.log('User:', user);

  const newUser = await client.user.create.mutate({
    name: 'Charlie',
    email: 'charlie@example.com',
  });
  console.log('Created:', newUser);
}

main().catch(console.error);
text
┌─────────────────────────────────────────────────────────────┐
│                    客户端调用方式                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Query(查询):                                             │
│  client.router.procedure.query({ input })                   │
│                                                             │
│  Mutation(变更):                                          │
│  client.router.procedure.mutate({ input })                  │
│                                                             │
│  示例:                                                     │
│  // 获取用户列表                                            │
│  const users = await client.user.list.query();              │
│                                                             │
│  // 获取单个用户                                            │
│  const user = await client.user.getById.query({ id: 1 });   │
│                                                             │
│  // 创建用户                                                │
│  const newUser = await client.user.create.mutate({          │
│    name: 'John',                                            │
│    email: 'john@example.com'                                │
│  });                                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

React 集成 #

配置 Provider #

tsx
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { trpc } from './trpc';

const queryClient = new QueryClient();

function App() {
  const [trpcClient] = React.useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: 'http://localhost:4000/trpc',
        }),
      ],
    })
  );

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <UserList />
      </QueryClientProvider>
    </trpc.Provider>
  );
}

使用 Hooks #

tsx
import { trpc } from './trpc';

function UserList() {
  const usersQuery = trpc.user.list.useQuery();
  const createUser = trpc.user.create.useMutation();

  if (usersQuery.isLoading) {
    return <div>Loading...</div>;
  }

  if (usersQuery.isError) {
    return <div>Error: {usersQuery.error.message}</div>;
  }

  const handleCreate = async () => {
    await createUser.mutateAsync({
      name: 'New User',
      email: 'new@example.com',
    });
    usersQuery.refetch();
  };

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {usersQuery.data?.map((user) => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
      <button onClick={handleCreate}>Create User</button>
    </div>
  );
}
text
┌─────────────────────────────────────────────────────────────┐
│                    React Hooks                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Query Hooks:                                               │
│  trpc.router.procedure.useQuery(input, options)             │
│                                                             │
│  Mutation Hooks:                                            │
│  trpc.router.procedure.useMutation(options)                 │
│                                                             │
│  特性:                                                     │
│  ✅ 自动类型推断                                            │
│  ✅ 自动缓存                                                │
│  ✅ 自动重新获取                                            │
│  ✅ 乐观更新                                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Next.js 集成 #

项目结构 #

text
nextjs-trpc/
├── src/
│   ├── pages/
│   │   ├── api/
│   │   │   └── trpc/
│   │   │       └── [trpc].ts    # API 路由
│   │   ├── _app.tsx             # Provider 配置
│   │   └── index.tsx            # 首页
│   └── server/
│       ├── routers/
│       │   └── index.ts
│       └── trpc.ts
└── package.json

API 路由 #

创建 src/pages/api/trpc/[trpc].ts

typescript
import * as trpcNext from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers';
import { createContext } from '../../../server/context';

export default trpcNext.createNextApiHandler({
  router: appRouter,
  createContext,
});

App 配置 #

创建 src/pages/_app.tsx

typescript
import type { AppType } from 'next/app';
import { trpc } from '../utils/trpc';

const MyApp: AppType = ({ Component, pageProps }) => {
  return <Component {...pageProps} />;
};

export default trpc.withTRPC(MyApp);

tRPC 配置 #

创建 src/utils/trpc.ts

typescript
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import type { AppRouter } from '../server/routers';

function getBaseUrl() {
  if (typeof window !== 'undefined') {
    return '';
  }
  if (process.env.VERCEL_URL) {
    return `https://${process.env.VERCEL_URL}`;
  }
  return `http://localhost:${process.env.PORT ?? 3000}`;
}

export const trpc = createTRPCNext<AppRouter>({
  config({ ctx }) {
    return {
      links: [
        httpBatchLink({
          url: `${getBaseUrl()}/api/trpc`,
        }),
      ],
    };
  },
  ssr: true,
});

运行项目 #

启动服务端 #

bash
npm run dev

测试 API #

bash
curl http://localhost:4000/trpc/user.list

curl -X POST http://localhost:4000/trpc/user.create \
  -H "Content-Type: application/json" \
  -d '{"0":{"json":{"name":"Test","email":"test@example.com"}}}'

调试技巧 #

使用 tRPC DevTools #

typescript
import { createTRPCReact } from '@trpc/react-query';

export const trpc = createTRPCReact<AppRouter>({
  overrides: {
    useMutation: {
      async onSuccess(opts) {
        console.log('Mutation success:', opts);
      },
    },
  },
});

日志中间件 #

typescript
import { initTRPC } from '@trpc/server';

const t = initTRPC.create();

const loggerMiddleware = t.middleware(async ({ path, type, next }) => {
  const start = Date.now();
  const result = await next();
  const duration = Date.now() - start;
  
  console.log(`[${type}] ${path} - ${duration}ms`);
  
  return result;
});

export const publicProcedure = t.procedure.use(loggerMiddleware);

常见问题 #

1. 类型推断不工作 #

确保正确导出 AppRouter 类型:

typescript
export type AppRouter = typeof appRouter;

2. CORS 错误 #

配置 CORS:

typescript
app.use(cors({
  origin: 'http://localhost:3000',
  credentials: true,
}));

3. 批量请求 #

tRPC 默认支持批量请求:

typescript
const [users, posts] = await Promise.all([
  client.user.list.query(),
  client.post.list.query(),
]);

下一步 #

现在你已经完成了 tRPC 的快速入门,接下来学习 路由器与过程,深入了解 tRPC 的核心概念!

最后更新:2026-03-29