trpc

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

tRPC Best Practices

tRPC 最佳实践

You are an expert in tRPC v11, TypeScript, and Next.js development. tRPC enables end-to-end typesafe APIs, allowing you to build and consume APIs without schemas, code generation, or runtime errors.
您是tRPC v11、TypeScript和Next.js开发领域的专家。tRPC支持端到端的类型安全API,让您无需编写模式、生成代码或担心运行时错误即可构建和使用API。

Requirements

要求

  • TypeScript >= 5.7.2
  • Strict TypeScript mode enabled
  • TypeScript >= 5.7.2
  • 启用TypeScript严格模式

Project Structure

项目结构

src/
  pages/
    _app.tsx              # createTRPCNext setup
    api/
      trpc/
        [trpc].ts         # tRPC HTTP handler
  server/
    routers/
      _app.ts             # Main router
      [feature].ts        # Feature-specific routers
    context.ts            # App context
    trpc.ts               # Procedure helpers
  utils/
    trpc.ts               # Typesafe React hooks
src/
  pages/
    _app.tsx              # createTRPCNext setup
    api/
      trpc/
        [trpc].ts         # tRPC HTTP handler
  server/
    routers/
      _app.ts             # Main router
      [feature].ts        # Feature-specific routers
    context.ts            # App context
    trpc.ts               # Procedure helpers
  utils/
    trpc.ts               # Typesafe React hooks

Server-Side Setup

服务端设置

Initialize tRPC Backend

初始化tRPC后端

Initialize tRPC backend once per application. Export reusable router and procedure helpers:
typescript
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';

const t = initTRPC.context<Context>().create({
  transformer: superjson,
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAdmin);
在应用中仅初始化一次tRPC后端。导出可复用的路由和过程助手:
typescript
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';

const t = initTRPC.context<Context>().create({
  transformer: superjson,
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAdmin);

Create Feature Routers

创建功能路由

Organize routers by feature/domain. Use Zod for input validation:
typescript
// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';

export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input, ctx }) => {
      return ctx.db.user.findUnique({ where: { id: input.id } });
    }),

  update: protectedProcedure
    .input(z.object({
      name: z.string().min(1),
      email: z.string().email(),
    }))
    .mutation(async ({ input, ctx }) => {
      return ctx.db.user.update({
        where: { id: ctx.user.id },
        data: input,
      });
    }),
});
按功能/领域组织路由。使用Zod进行输入验证:
typescript
// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure, protectedProcedure } from '../trpc';

export const userRouter = router({
  getById: publicProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ input, ctx }) => {
      return ctx.db.user.findUnique({ where: { id: input.id } });
    }),

  update: protectedProcedure
    .input(z.object({
      name: z.string().min(1),
      email: z.string().email(),
    }))
    .mutation(async ({ input, ctx }) => {
      return ctx.db.user.update({
        where: { id: ctx.user.id },
        data: input,
      });
    }),
});

Main App Router

主应用路由

typescript
// server/routers/_app.ts
import { router } from '../trpc';
import { userRouter } from './user';
import { postRouter } from './post';

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

export type AppRouter = typeof appRouter;
typescript
// server/routers/_app.ts
import { router } from '../trpc';
import { userRouter } from './user';
import { postRouter } from './post';

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

export type AppRouter = typeof appRouter;

Client-Side Setup

客户端设置

Configure tRPC Client

配置tRPC客户端

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

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() {
    return {
      transformer: superjson,
      links: [
        httpBatchLink({
          url: `${getBaseUrl()}/api/trpc`,
          maxURLLength: 2083,
        }),
      ],
    };
  },
  ssr: false,
});
typescript
// utils/trpc.ts
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import superjson from 'superjson';
import type { AppRouter } from '../server/routers/_app';

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() {
    return {
      transformer: superjson,
      links: [
        httpBatchLink({
          url: `${getBaseUrl()}/api/trpc`,
          maxURLLength: 2083,
        }),
      ],
    };
  },
  ssr: false,
});

Key Best Practices

核心最佳实践

1. Input Validation

1. 输入验证

Always use Zod for type safety and runtime validation on all procedure inputs:
typescript
.input(z.object({
  email: z.string().email(),
  age: z.number().min(0).max(120),
}))
始终使用Zod为所有过程输入提供类型安全和运行时验证:
typescript
.input(z.object({
  email: z.string().email(),
  age: z.number().min(0).max(120),
}))

2. Router Organization

2. 路由组织

Structure routers by feature/domain rather than one monolithic router. Each feature should have its own router file.
按功能/领域构建路由,而非使用单一的大型路由。每个功能应拥有独立的路由文件。

3. Middleware Implementation

3. 中间件实现

Implement middleware for authentication, logging, and cross-cutting concerns:
typescript
const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({ ctx: { user: ctx.user } });
});
为身份验证、日志记录和横切关注点实现中间件:
typescript
const isAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.user) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }
  return next({ ctx: { user: ctx.user } });
});

4. Error Handling

4. 错误处理

Use TRPCError for consistent, informative error responses:
typescript
import { TRPCError } from '@trpc/server';

throw new TRPCError({
  code: 'NOT_FOUND',
  message: 'User not found',
});
使用TRPCError返回一致且信息丰富的错误响应:
typescript
import { TRPCError } from '@trpc/server';

throw new TRPCError({
  code: 'NOT_FOUND',
  message: 'User not found',
});

5. Data Transformers

5. 数据转换器

Apply SuperJSON for automatic serialization of dates, Maps, Sets, and other JavaScript types:
typescript
import superjson from 'superjson';

const t = initTRPC.create({
  transformer: superjson,
});
使用SuperJSON自动序列化日期、Map、Set和其他JavaScript类型:
typescript
import superjson from 'superjson';

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

6. React Query Integration

6. React Query集成

Leverage tRPC's built-in React Query utilities for data fetching, mutations, and caching:
typescript
// Queries
const { data, isLoading } = trpc.user.getById.useQuery({ id: '123' });

// Mutations
const mutation = trpc.user.update.useMutation({
  onSuccess: () => {
    utils.user.getById.invalidate();
  },
});

// Prefetching
await utils.user.getById.prefetch({ id: '123' });
利用tRPC内置的React Query工具进行数据获取、变更和缓存:
typescript
// Queries
const { data, isLoading } = trpc.user.getById.useQuery({ id: '123' });

// Mutations
const mutation = trpc.user.update.useMutation({
  onSuccess: () => {
    utils.user.getById.invalidate();
  },
});

// Prefetching
await utils.user.getById.prefetch({ id: '123' });

7. Context Creation

7. 上下文创建

Share resources (database connections, user sessions) across procedures via context:
typescript
// server/context.ts
export async function createContext({ req, res }: CreateNextContextOptions) {
  const user = await getUser(req);
  return {
    db: prisma,
    user,
    req,
    res,
  };
}

export type Context = Awaited<ReturnType<typeof createContext>>;
通过上下文在各个过程之间共享资源(数据库连接、用户会话等):
typescript
// server/context.ts
export async function createContext({ req, res }: CreateNextContextOptions) {
  const user = await getUser(req);
  return {
    db: prisma,
    user,
    req,
    res,
  };
}

export type Context = Awaited<ReturnType<typeof createContext>>;

8. Type Exports

8. 类型导出

Export only type signatures to client code, never router implementations:
typescript
// Only export the type
export type AppRouter = typeof appRouter;

// Never export the actual router to client code
仅向客户端代码导出类型签名,绝不导出路由实现:
typescript
// Only export the type
export type AppRouter = typeof appRouter;

// Never export the actual router to client code

9. Authorization Levels

9. 授权级别

Create distinct procedure types for different authorization levels:
typescript
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAuthed).use(isAdmin);
为不同的授权级别创建不同的过程类型:
typescript
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAuthed).use(isAdmin);

10. Performance Optimization

10. 性能优化

  • Enable request batching with httpBatchLink
  • Configure maxURLLength to prevent URL length issues
  • Implement prefetching for static/predictable data
  • Use React Query's caching strategies
  • 使用httpBatchLink启用请求批处理
  • 配置maxURLLength以避免URL长度问题
  • 为静态/可预测数据实现预取
  • 使用React Query的缓存策略

Anti-Patterns to Avoid

需要避免的反模式

  • Do not use
    any
    types - leverage tRPC's full type inference
  • Do not skip input validation
  • Do not expose internal errors to clients
  • Do not mix server and client code
  • Do not create overly large routers - split by feature
  • 不要使用
    any
    类型 - 充分利用tRPC的完整类型推断
  • 不要跳过输入验证
  • 不要向客户端暴露内部错误
  • 不要混合服务端和客户端代码
  • 不要创建过大的路由 - 按功能拆分