trpc — правило для Cursor: что делает и как установить
Задаёт стандарты построения API на tRPC в файлах TypeScript. Помогает генерировать модульные роутеры, валидацию через Zod и корректные middleware.
.cursor/rules/trpc.mdcЧто делает trpc
Правило нацелено на файлы по маске **/*.{ts,tsx} и диктует чёткую структуру бэкенда на tRPC. Вместо раздутых монолитных файлов ассистент учится делить роутеры по предметным областям, объединяя их в корневом appRouter. Контекст приложения с сессиями и базой данных фиксируется один раз в функции createTRPCContext.
В инструкциях прописана обязательная проверка входных данных через схемы Zod, запрещено использование z.any(). Для разграничения доступа правило требует подключать middleware (включая проверку авторизации через TRPCError), а для сохранения типов сложных структур вроде Date или Map предписывает настраивать трансформер superjson.
Обратите внимание: правило запрещает использовать пути с алиасами вроде @/server/... при импорте типов роутера на клиенте, требуя относительные пути во избежание сброса типов в any.
Какие задачи решает
- разбить разросшийся монолитный роутер на отдельные модули
- настроить строгую валидацию входных параметров через Zod
- организовать middleware для проверки сессии и роли администратора
- предотвратить потерю типов при импорте AppRouter на клиенте
Примеры запросов агенту
- Создай роутер для работы с постами блога с валидацией через Zod
- Напиши процедуру удаления пользователя, доступную только администратору
- Настрой создание контекста tRPC с базой данных и сессией
Как установить trpc
Вручную: положить файлы
.cursor/rules/trpc.mdcВо frontmatter: description, globs, alwaysApply
Вручную: положить файлы
.claude/rules/trpc.mdПоле paths во frontmatter ограничивает файлы: globs из источника → paths
AGENTS.md
Codex читает AGENTS.md: вставьте текст правила туда
Вручную: положить файлы
.github/instructions/trpc.instructions.mdПоле applyTo задаёт, к каким файлам применять: globs из источника → applyTo
Вручную: положить файлы
.devin/rules/trpc.mdВручную: положить файлы
.clinerules/trpc.mdПо шагам, с командами для каждой платформы: Cursor rules: файлы .mdc, четыре режима и замена .cursorrules
Вопросы о trpc
Как установить trpc в Cursor?
Сохраните файл правила по пути .cursor/rules/trpc.mdc в корне вашего проекта.
Что делает правило trpc?
Оно обучает ИИ-ассистента архитектурным соглашениям tRPC: структурированию роутеров по фичам, схематической валидации входных данных, форматированию ошибок и правильному сериализатору superjson.
Почему правило требует относительные импорты для AppRouter?
TypeScript иногда даёт сбой при разрешении алиасов путей для вывода типов tRPC, из-за чего типы на клиенте могут превратиться в any.
Исходный текст
trpc.mdc · CC0-1.0
---
description: Definitive guidelines for building robust, type-safe, and maintainable tRPC APIs, emphasizing modularity, Zod validation, and efficient error handling.
globs: **/*.{ts,tsx}
---
# tRPC Best Practices
These rules provide a definitive guide for building tRPC APIs within our team. Adhere to these principles for consistent, high-quality, and type-safe backend development.
## 1. Modularize Routers by Feature
Organize your API into distinct, feature-specific routers. This improves maintainability, readability, and allows for easier scaling.
❌ **BAD: Monolithic Router**
```typescript
// src/server/api/root.ts
export const appRouter = t.router({
getUser: publicProcedure.query(() => { /* ... */ }),
createUser: protectedProcedure.mutation(() => { /* ... */ }),
getPost: publicProcedure.query(() => { /* ... */ }),
createPost: protectedProcedure.mutation(() => { /* ... */ }),
// ... hundreds of procedures
});
```
✅ **GOOD: Modular Routers**
```typescript
// src/server/api/routers/user.ts
export const userRouter = t.router({
getUser: publicProcedure.query(() => { /* ... */ }),
createUser: protectedProcedure.mutation(() => { /* ... */ }),
});
// src/server/api/routers/post.ts
export const postRouter = t.router({
getPost: publicProcedure.query(() => { /* ... */ }),
createPost: protectedProcedure.mutation(() => { /* ... */ }),
});
// src/server/api/root.ts
export const appRouter = t.router({
user: userRouter,
post: postRouter,
});
```
## 2. Centralize Context Definition
Define your `createTRPCContext` function once to provide shared resources (e.g., database client, session, headers) to all procedures. This ensures consistency and avoids prop-drilling or global state.
```typescript
// src/server/api/context.ts
import { type inferAsyncReturnType } from '@trpc/server';
import { type CreateNextContextOptions } from '@trpc/server/adapters/next';
import { db } from '../db'; // Your database client
import { getServerAuthSession } from '../auth'; // Your auth session helper
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
const session = await getServerAuthSession({ req, res });
return {
db,
session,
headers: opts.req.headers,
};
};
export type Context = inferAsyncReturnType<typeof createTRPCContext>;
```
## 3. Enforce Input Validation with Zod
Always validate procedure inputs using Zod schemas. This provides runtime validation mirroring compile-time types, preventing malformed data from reaching business logic and improving API robustness.
❌ **BAD: No Input Validation**
```typescript
export const postRouter = t.router({
createPost: protectedProcedure
.input(z.any()) // Avoid z.any()
.mutation(async ({ ctx, input }) => {
// Manual validation or no validation
if (typeof input.title !== 'string' || input.title.length < 3) {
throw new Error('Invalid title');
}
return ctx.db.post.create({ data: input });
}),
});
```
✅ **GOOD: Zod for Input Validation**
```typescript
import { z } from 'zod';
export const postRouter = t.router({
createPost: protectedProcedure
.input(z.object({
title: z.string().min(3, "Title must be at least 3 characters"),
content: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
// Input is guaranteed to be type-safe and validated
return ctx.db.post.create({ data: input });
}),
});
```
## 4. Implement Middleware for Authorization
Use tRPC middleware to enforce authentication and authorization rules declaratively. This keeps procedure logic clean and ensures consistent access control.
```typescript
// src/server/api/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import superjson from 'superjson';
import { ZodError } from 'zod';
import { type Context } from './context';
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
export const createTRPCRouter = t.router;
export const publicProcedure = t.procedure;
// Middleware to check for authenticated session
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
// Infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
// Example: Admin-only procedure
const enforceUserIsAdmin = enforceUserIsAuthed.unstable_pipe(({ ctx, next }) => {
if (ctx.session.user.role !== 'ADMIN') { // Assuming a 'role' on user
throw new TRPCError({ code: 'FORBIDDEN', message: 'Only admins can perform this action.' });
}
return next();
});
export const adminProcedure = t.procedure.use(enforceUserIsAdmin);
```
## 5. Standardize Error Formatting
Configure `errorFormatter` in `initTRPC.create` to provide consistent and helpful error responses, especially for Zod validation errors.
```typescript
// src/server/api/trpc.ts (excerpt)
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
// Flatten Zod errors for client-side consumption
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
```
## 6. Use Superjson for Data Serialization
Always use `superjson` as your data transformer. It correctly serializes and deserializes complex types (like `Date`, `Map`, `Set`) between client and server, maintaining type fidelity.
```typescript
// src/server/api/trpc.ts (excerpt)
import superjson from 'superjson';
const t = initTRPC.context<Context>().create({
transformer: superjson, // Essential for type-safe serialization
// ...
});
```
## 7. Prefer Relative Imports for tRPC Types
When importing `AppRouter` or other tRPC-related types into your client-side code, use relative paths instead of TypeScript path aliases. tRPC's type inference can sometimes struggle with alias resolution, leading to `any` types.
❌ **BAD: Path Alias for tRPC Types**
```typescript
// src/utils/trpc.ts
import { type AppRouter } from '@/server/api/root'; // May resolve to 'any'
```
✅ **GOOD: Relative Path for tRPC Types**
```typescript
// src/utils/trpc.ts
import { type AppRouter } from '../../server/api/root'; // Ensures correct type inference
```
## 8. Implement Robust Client-Side Invalidation
When using tRPC with `@tanstack/react-query`, ensure proper cache invalidation after mutations. This prevents stale data from being displayed and ensures UI consistency.
```typescript
// src/components/PostForm.tsx
import { api } from '@/utils/api'; // Your tRPC client instance
function PostForm() {
const utils = api.useUtils(); // Access TanStack Query client
const createPost = api.post.createPost.useMutation({
onSuccess: () => {
// Invalidate all queries under the 'post' router to refetch lists/details
utils.post.invalidate();
// For more granular control:
// utils.post.getPosts.invalidate();
},
});
// ... form submission logic
const handleSubmit = (data: { title: string; content?: string }) => {
createPost.mutate(data);
};
return (/* ... */);
}
```
## 9. Document Procedures with JSDoc
Add comprehensive JSDoc comments to each procedure. This improves developer experience by providing immediate context and usage instructions in the IDE.
```typescript
// src/server/api/routers/post.ts
export const postRouter = t.router({
/**
* Fetches all posts from the database.
* @returns A list of posts.
*/
getPosts: publicProcedure
.query(async ({ ctx }) => {
return ctx.db.post.findMany();
}),
/**
* Creates a new post.
* @param input - The post data (title, content).
* @returns The newly created post.
* @throws TRPCError if user is unauthorized.
*/
createPost: protectedProcedure
.input(z.object({
title: z.string().min(3),
content: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
return ctx.db.post.create({ data: { ...input, authorId: ctx.session.user.id } });
}),
});
```
## 10. Enable TypeScript Strict Mode
Always develop with TypeScript's `strict` mode enabled in `tsconfig.json`. This catches a wide range of common programming errors at compile time, maximizing tRPC's type-safety benefits.
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true, // Essential for robust type-safety
// ... other options
},
// ...
}
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc