tRPC چیست؟ آموزش ساخت API کاملاً Type-Safe با TypeScript، Node.js و React
در این آموزش tRPC را از صفر راهاندازی میکنیم و با TypeScript، Node.js، Express، React، Zod و TanStack Query یک API کاملاً Type-Safe برای برنامه هوش مصنوعی میسازیم.
در یک برنامه Full-Stack معمولاً Backend و Frontend بهصورت جداگانه توسعه داده میشوند. Backend یک API میسازد و Frontend باید دقیقاً بداند هر Endpoint چه ورودیهایی میپذیرد و چه خروجیای برمیگرداند.
اگر Backend تغییر کند اما Typeهای Frontend بهروزرسانی نشوند، مشکلاتی مانند این موارد ایجاد میشوند:
- Frontend فیلدی را ارسال میکند که دیگر وجود ندارد.
- Backend ساختار Response را تغییر میدهد.
- یک مقدار اختیاری به فیلد اجباری تبدیل میشود.
- نام Endpoint یا پارامتر تغییر میکند.
- Client همچنان Typeهای قدیمی را استفاده میکند.
- خطا فقط هنگام اجرای برنامه مشخص میشود.
در REST API میتوان این مشکل را با OpenAPI و تولید SDK کاهش داد. در GraphQL نیز Schema و Code Generator میتوانند Typeهای Client را تولید کنند. tRPC راه متفاوتی ارائه میدهد: وقتی Frontend و Backend هر دو با TypeScript نوشته شدهاند، Type روتر Backend مستقیماً در Client استفاده میشود.
در این مقاله tRPC را از صفر راهاندازی میکنیم، Query و Mutation میسازیم، ورودیها را با Zod اعتبارسنجی میکنیم و یک برنامه React را به Backend متصل میکنیم. در پایان نیز یک Procedure واقعی برای اتصال امن به API هوش مصنوعی درواره خواهیم ساخت.
tRPC چیست؟
tRPC یک فریمورک برای ساخت APIهای End-to-End Type-Safe در پروژههای TypeScript است. با tRPC میتوان Procedureهای Backend را تعریف کرد و سپس ورودی و خروجی همان Procedureها را بدون تعریف Type تکراری یا تولید فایل SDK در Client استفاده کرد.
نمونه Procedure در Backend:
const userRouter = router({
byId: publicProcedure
.input(
z.object({
id: z.string(),
}),
)
.query(({ input }) => {
return {
id: input.id,
name: "Sara",
active: true,
};
}),
});
در Frontend:
const user = await trpc.user.byId.query({
id: "usr_123",
});
TypeScript بهصورت خودکار میداند:
- Procedure با نام
user.byIdوجود دارد. - ورودی باید یک Object شامل
idاز نوع String باشد. - خروجی دارای
id،nameوactiveاست. activeاز نوع Boolean است.- ارسال فیلد اشتباه یا استفاده نادرست از خروجی باعث خطای TypeScript میشود.
tRPC برای ایجاد این Type Safety به تولید فایل جداگانه نیاز ندارد. Type مربوط به Router از Backend بهصورت Type-Only در Client وارد میشود.
براساس راهنمای رسمی tRPC، Client با دریافت Type مربوط به AppRouter میتواند Procedureها، ورودیها و خروجیها را استنباط کند و در ویرایشگر Autocomplete ارائه دهد.
عبارت tRPC مخفف چیست؟
نام tRPC از ترکیب TypeScript و RPC گرفته شده است. RPC مخفف Remote Procedure Call است؛ یعنی Client یک تابع یا Procedure را که روی Server قرار دارد، از راه دور فراخوانی میکند.
در tRPC، فراخوانی Client ممکن است چنین باشد:
await trpc.article.create.mutate({
title: "آموزش tRPC",
content: "متن مقاله",
});
این کد از نظر تجربه توسعه شبیه فراخوانی یک تابع TypeScript است، اما در عمل Client یک درخواست HTTP به Server ارسال میکند.
tRPC چگونه کار میکند؟
گردش ساده یک درخواست tRPC به این شکل است:
- Backend یک Router تعریف میکند.
- Router شامل Query، Mutation یا Subscription است.
- هر Procedure میتواند Input Validator داشته باشد.
- Type کامل Router با
typeof appRouterاستخراج میشود. - Client این Type را با
import typeدریافت میکند. - tRPC Client درخواست HTTP را میسازد.
- Adapter سمت Server درخواست را به Procedure مناسب هدایت میکند.
- نتیجه به Client برگردانده میشود.
- TypeScript نوع نتیجه را بدون Type دستی تشخیص میدهد.
نکته مهم این است که کد Backend به Frontend منتقل نمیشود. فقط Type مربوط به Router در زمان Compile استفاده و سپس حذف میشود.
مزایای tRPC
Type Safety سراسری
ورودی و خروجی API از Backend تا Frontend Type-Safe است.
const result =
await trpc.chat.create.mutate({
prompt: "سلام",
modelId: "YOUR_MODEL_ID",
});
اگر modelId حذف یا به نوع اشتباه ارسال شود، TypeScript قبل از اجرا هشدار میدهد.
حذف Typeهای تکراری
در پروژههای معمولی ممکن است Typeهای مشابه در چند محل تعریف شوند:
// Backend
type CreateUserInput = {
name: string;
email: string;
};
// Frontend
type CreateUserRequest = {
name: string;
email: string;
};
با tRPC، Type ورودی از Procedure استنباط میشود و نیازی به نگهداری دو تعریف مستقل نیست.
Autocomplete کامل
با نوشتن:
trpc.
ویرایشگر Routerها و Procedureهای موجود را پیشنهاد میدهد.
Refactoring سادهتر
اگر نام فیلد یا Procedure تغییر کند، TypeScript محلهای ناسازگار در Client را نشان میدهد.
هماهنگی با Zod
tRPC از Validatorهای مختلف پشتیبانی میکند و Zod را بهعنوان انتخاب پیشنهادی معرفی میکند. Schema ورودی هم داده Runtime را اعتبارسنجی میکند و هم Type ورودی را میسازد. جزئیات در مستندات Input & Output Validators آمده است.
هماهنگی با TanStack Query
در React میتوان tRPC را با TanStack Query ترکیب کرد و از Cache، Loading State، Mutation، Retry و Invalidation استفاده کرد.
محدودیتهای tRPC
tRPC برای تمام APIها بهترین انتخاب نیست.
وابستگی Client و Server به TypeScript
مزیت اصلی tRPC زمانی به دست میآید که Client و Server هر دو TypeScript باشند. اگر API باید توسط Python، PHP، Java، Go یا مشتریان خارجی استفاده شود، REST و OpenAPI یا GraphQL ممکن است قرارداد عمومی مناسبتری ارائه دهند.
اتصال نسبتاً نزدیک Frontend و Backend
Frontend مستقیماً به Type روتر Backend وابسته است. این ویژگی در Monorepo یا محصول Full-Stack بسیار مفید است، اما برای API عمومی و تیمهای کاملاً مستقل ممکن است محدودکننده باشد.
نبود قرارداد مستقل عمومی به شکل پیشفرض
در REST میتوان یک فایل OpenAPI منتشر کرد. در GraphQL نیز Schema قابل Introspection است. در tRPC قرارداد اصلی همان TypeScript Router است.
مناسب نبودن برای API عمومی چندزبانه
اگر توسعهدهندگان خارج سازمان باید با زبانهای مختلف به API متصل شوند، بهتر است یک لایه REST، OpenAPI یا GraphQL عمومی نیز در نظر گرفته شود.
Type Safety فقط در Compile Time کافی نیست
حتی با tRPC باید ورودی Runtime اعتبارسنجی شود. به همین دلیل از Zod یا Validator مشابه استفاده میکنیم.
tRPC، REST و GraphQL چه تفاوتی دارند؟
| معیار | tRPC | REST | GraphQL |
|---|---|---|---|
| مدل ارتباط | Procedure | Endpoint و Resource | Query و Schema |
| Type Safety خودکار | بسیار قوی در TypeScript | با OpenAPI و Codegen | با Schema و Codegen |
| مناسب API عمومی | محدودتر | بسیار مناسب | مناسب |
| پشتیبانی چند زبان | محدودتر | بسیار گسترده | گسترده |
| نیاز به Code Generation | معمولاً ندارد | برای SDK اغلب دارد | برای Client Type اغلب دارد |
| انتخاب فیلد توسط Client | محدود به Procedure | محدود به Endpoint | دارد |
| بهترین کاربرد | برنامه Full-Stack TypeScript | API عمومی و استاندارد | دادههای گرافی و Clientهای متنوع |
| پیچیدگی شروع | کم تا متوسط | کم | متوسط |
انتخاب ابزار باید براساس معماری محصول انجام شود:
- اگر Frontend و Backend در یک پروژه TypeScript هستند، tRPC بسیار جذاب است.
- اگر API برای مشتریان عمومی و چند زبان ارائه میشود، REST و OpenAPI انتخاب رایجتری هستند.
- اگر Client باید ساختار داده مورد نیازش را انتخاب کند، GraphQL میتواند مناسب باشد.
- در بعضی سیستمها میتوان tRPC را برای داشبورد داخلی و REST را برای API عمومی استفاده کرد.
مفاهیم اصلی tRPC
Router
Router مجموعهای از Procedureها یا Routerهای کوچکتر است.
const appRouter = router({
user: userRouter,
article: articleRouter,
chat: chatRouter,
});
Procedure
Procedure تابعی است که از Client قابل فراخوانی است.
طبق مستندات رسمی Procedureها، Procedure میتواند یکی از این انواع باشد:
- Query برای دریافت داده
- Mutation برای ایجاد، ویرایش یا حذف داده
- Subscription برای جریان داده بلادرنگ
Context
Context دادهای است که در هر Procedure قابل دسترسی خواهد بود، مانند:
- اتصال دیتابیس
- کاربر فعلی
- Session
- Request ID
- Logger
- تنظیمات برنامه
Middleware
Middleware پیش از اجرای Procedure اجرا میشود و برای موارد مشترکی مانند Logging، بررسی Session یا اندازهگیری زمان درخواست کاربرد دارد.
Input Validator
ورودی درخواست را در Runtime بررسی میکند:
.input(
z.object({
id: z.string().uuid(),
}),
)
Output Validator
در صورت نیاز میتوان خروجی Procedure را نیز اعتبارسنجی کرد:
.output(
z.object({
id: z.string(),
name: z.string(),
}),
)
نسخه مورد استفاده در این آموزش
نمونههای این مقاله براساس tRPC 11 نوشته شدهاند. tRPC v11 از TanStack React Query جدید پشتیبانی میکند و برای استفاده کامل از آن باید نسخههای هماهنگ کتابخانهها را نصب کنید.
در پروژههای قدیمی، پیش از ارتقا راهنمای مهاجرت از tRPC 10 به tRPC 11 را بررسی کنید.
معماری پروژه عملی
ساختار پیشنهادی:
trpc-ai-app/
apps/
server/
src/
context.ts
server.ts
trpc.ts
routers/
chat.router.ts
model.router.ts
index.ts
web/
src/
App.tsx
main.tsx
trpc.tsx
components/
ChatForm.tsx
ModelList.tsx
packages/
shared/
package.json
برای ساده نگه داشتن آموزش، Server و Client را جدا میسازیم؛ اما Type مربوط به AppRouter باید برای Client قابل دسترسی باشد. در پروژه واقعی بهتر است از Monorepo مانند npm Workspaces، pnpm Workspaces یا Turborepo استفاده شود.
ساخت Backend با Node.js و Express
پوشه Server را بسازید:
mkdir trpc-server
cd trpc-server
npm init -y
وابستگیها:
npm install @trpc/server zod express cors dotenv
npm install --save-dev typescript tsx @types/node @types/express @types/cors
در package.json:
{
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"typecheck": "tsc --noEmit"
}
}
فایل tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src"
},
"include": [
"src"
]
}
راهاندازی tRPC
فایل src/trpc.ts:
import { initTRPC } from "@trpc/server";
type Context = {
requestId: string;
user: {
id: string;
name: string;
} | null;
};
const t = initTRPC
.context<Context>()
.create({
isDev:
process.env.NODE_ENV !== "production",
});
export const router = t.router;
export const publicProcedure = t.procedure;
export const middleware = t.middleware;
در این فایل:
- Shape مربوط به Context تعریف شده است.
- tRPC با Context مورد نظر راهاندازی شده است.
- Router و Procedure پایه صادر شدهاند.
- نمایش جزئیات Development براساس محیط کنترل میشود.
ساخت Context
فایل src/context.ts:
import type {
CreateExpressContextOptions,
} from "@trpc/server/adapters/express";
export async function createContext({
req,
}: CreateExpressContextOptions) {
const requestId =
req.header("x-request-id") ??
crypto.randomUUID();
const userId =
req.header("x-demo-user-id");
const user = userId
? {
id: userId,
name: "Demo User",
}
: null;
return {
requestId,
user,
};
}
export type Context = Awaited<
ReturnType<typeof createContext>
>;
استفاده از x-demo-user-id در این مثال فقط برای آموزش Context است و احراز هویت واقعی محسوب نمیشود. در پروژه واقعی، Context باید اطلاعات کاربر را از Session یا سازوکار احراز هویت معتبر برنامه دریافت کند.
ساخت اولین Router
فایل src/routers/model.router.ts:
import { z } from "zod";
import {
publicProcedure,
router,
} from "../trpc.js";
const models = [
{
id: "model-text-pro",
name: "مدل متنی حرفهای",
category: "text",
},
{
id: "model-code-pro",
name: "مدل برنامهنویسی",
category: "coding",
},
{
id: "model-reasoning-pro",
name: "مدل استدلالی",
category: "reasoning",
},
];
export const modelRouter = router({
list: publicProcedure
.input(
z
.object({
category: z
.enum([
"text",
"coding",
"reasoning",
])
.optional(),
})
.optional(),
)
.query(({ input }) => {
if (!input?.category) {
return models;
}
return models.filter(
(model) =>
model.category === input.category,
);
}),
byId: publicProcedure
.input(
z.object({
id: z.string().min(1),
}),
)
.query(({ input }) => {
return (
models.find(
(model) => model.id === input.id,
) ?? null
);
}),
});
در این Router دو Query داریم:
model.listmodel.byId
Type ورودی و خروجی هر دو توسط TypeScript استنباط میشوند.
Query در tRPC چیست؟
Query برای دریافت دادهای استفاده میشود که معمولاً Side Effect ندارد:
const articleRouter = router({
list: publicProcedure.query(() => {
return [
{
id: "1",
title: "آموزش TypeScript",
},
];
}),
});
فراخوانی Client:
const articles =
await trpc.article.list.query();
Mutation در tRPC چیست؟
Mutation برای عملیات ایجاد، ویرایش، حذف یا اجرای یک فرایند استفاده میشود:
const articleRouter = router({
create: publicProcedure
.input(
z.object({
title: z.string().min(5),
content: z.string().min(20),
}),
)
.mutation(({ input }) => {
return {
id: crypto.randomUUID(),
...input,
createdAt: new Date(),
};
}),
});
فراخوانی Client:
const article =
await trpc.article.create.mutate({
title: "آموزش tRPC",
content:
"متن کامل مقاله آموزشی درباره tRPC",
});
اعتبارسنجی ورودی با Zod
Schema نمونه ساخت مکالمه:
const CreateChatInputSchema = z.object({
modelId: z.string().min(1),
prompt: z
.string()
.trim()
.min(1)
.max(10_000),
temperature: z
.number()
.min(0)
.max(2)
.optional(),
});
در Procedure:
create: publicProcedure
.input(CreateChatInputSchema)
.mutation(({ input }) => {
return {
modelId: input.modelId,
prompt: input.prompt,
temperature:
input.temperature ?? 0.7,
};
});
اگر Client این ورودی را ارسال کند:
{
modelId: "",
prompt: "",
temperature: 5
}
Zod درخواست را پیش از اجرای Resolver رد میکند.
TypeScript خطاهای قابل شناسایی در Compile Time را پیدا میکند و Zod داده واقعی Runtime را اعتبارسنجی میکند.
ساخت Protected Procedure
فایل src/trpc.ts را گسترش میدهیم:
import {
initTRPC,
TRPCError,
} from "@trpc/server";
type Context = {
requestId: string;
user: {
id: string;
name: string;
} | null;
};
const t = initTRPC
.context<Context>()
.create();
export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure =
t.procedure.use(({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({
code: "UNAUTHORIZED",
message:
"برای اجرای این عملیات باید وارد حساب شوید.",
});
}
return next({
ctx: {
...ctx,
user: ctx.user,
},
});
});
پس از Middleware، TypeScript میداند که ctx.user دیگر null نیست:
const profileRouter = router({
me: protectedProcedure.query(
({ ctx }) => {
return ctx.user;
},
),
});
راهنمای رسمی این الگو در مستندات Authorization در tRPC موجود است.
ساخت Middleware برای Logging
const loggedProcedure =
publicProcedure.use(
async ({ path, ctx, next }) => {
const startedAt = performance.now();
const result = await next();
const durationMs =
performance.now() - startedAt;
console.log({
requestId: ctx.requestId,
path,
durationMs:
Math.round(durationMs),
ok: result.ok,
});
return result;
},
);
استفاده:
const healthRouter = router({
check: loggedProcedure.query(() => {
return {
status: "ok",
timestamp: new Date(),
};
}),
});
در Production بهتر است Log ساختاریافته باشد و اطلاعات حساس، متن کامل پرامپت یا Secretها بدون سیاست مشخص ثبت نشوند.
ترکیب Routerها
فایل src/routers/index.ts:
import { router } from "../trpc.js";
import { modelRouter } from "./model.router.js";
export const appRouter = router({
model: modelRouter,
});
export type AppRouter =
typeof appRouter;
AppRouter مهمترین Type پروژه tRPC است. Client با استفاده از همین Type ساختار کامل API را میشناسد.
اتصال tRPC به Express
فایل src/server.ts:
import "dotenv/config";
import express from "express";
import cors from "cors";
import {
createExpressMiddleware,
} from "@trpc/server/adapters/express";
import { createContext } from "./context.js";
import { appRouter } from "./routers/index.js";
const app = express();
const port = Number(
process.env.PORT ?? 3000,
);
app.use(
cors({
origin: "http://localhost:5173",
credentials: true,
}),
);
app.get("/health", (_req, res) => {
res.json({
status: "ok",
});
});
app.use(
"/trpc",
createExpressMiddleware({
router: appRouter,
createContext,
onError({
error,
path,
ctx,
}) {
console.error({
requestId: ctx?.requestId,
path,
code: error.code,
message: error.message,
});
},
}),
);
app.listen(port, () => {
console.log(
`Server is running on http://localhost:${port}`,
);
});
اجرا:
npm run dev
Health Check:
curl http://localhost:3000/health
ساخت Client ساده tRPC
در یک پروژه TypeScript:
npm install @trpc/client
فایل src/client.ts:
import {
createTRPCClient,
httpBatchLink,
} from "@trpc/client";
import type {
AppRouter,
} from "../server/routers/index.js";
const trpc =
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url:
"http://localhost:3000/trpc",
}),
],
});
const models =
await trpc.model.list.query();
console.log(models);
باید AppRouter را با import type وارد کنید:
import type { AppRouter } from "...";
این کار باعث میشود کد اجرایی Server وارد Bundle مربوط به Client نشود.
httpBatchLink چیست؟
httpBatchLink چند عملیات tRPC را در صورت امکان داخل یک درخواست HTTP گروهبندی میکند.
const [models, profile] =
await Promise.all([
trpc.model.list.query(),
trpc.profile.me.query(),
]);
طبق مستندات HTTP Batch Link، این Link میتواند چند عملیات همزمان را در یک درخواست HTTP ارسال کند.
برای کنترل تعداد عملیات هر Batch:
httpBatchLink({
url: "http://localhost:3000/trpc",
maxItems: 10,
});
Batching همیشه به معنی یک Query دیتابیس نیست. هر Procedure ممکن است Query یا پردازش مستقل خود را اجرا کند.
ساخت برنامه React
با Vite پروژه React را بسازید:
npm create vite@latest trpc-web -- --template react-ts
cd trpc-web
npm install
وابستگیهای tRPC و TanStack Query:
npm install @trpc/client @trpc/server @trpc/tanstack-react-query @tanstack/react-query zod
در Monorepo معمولاً Type مربوط به AppRouter از یک Workspace داخلی دریافت میشود.
ساخت tRPC Provider در React
فایل src/trpc.tsx:
import {
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import {
createTRPCClient,
httpBatchLink,
} from "@trpc/client";
import {
createTRPCContext,
} from "@trpc/tanstack-react-query";
import {
useState,
type ReactNode,
} from "react";
import type {
AppRouter,
} from "../../server/src/routers/index";
export const {
TRPCProvider,
useTRPC,
useTRPCClient,
} = createTRPCContext<AppRouter>();
type Props = {
children: ReactNode;
};
export function AppProviders({
children,
}: Props) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
retry: 1,
},
},
}),
);
const [trpcClient] = useState(
() =>
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url:
"http://localhost:3000/trpc",
headers() {
return {
"x-demo-user-id":
"usr_demo",
};
},
}),
],
}),
);
return (
<QueryClientProvider
client={queryClient}
>
<TRPCProvider
trpcClient={trpcClient}
queryClient={queryClient}
>
{children}
</TRPCProvider>
</QueryClientProvider>
);
}
در src/main.tsx:
import { StrictMode } from "react";
import {
createRoot,
} from "react-dom/client";
import App from "./App";
import {
AppProviders,
} from "./trpc";
createRoot(
document.getElementById("root")!,
).render(
<StrictMode>
<AppProviders>
<App />
</AppProviders>
</StrictMode>,
);
ساختار بهروز اتصال tRPC 11 به React در مستندات TanStack React Query Setup توضیح داده شده است.
اجرای Query در React
فایل src/components/ModelList.tsx:
import {
useQuery,
} from "@tanstack/react-query";
import { useTRPC } from "../trpc";
export function ModelList() {
const trpc = useTRPC();
const modelsQuery = useQuery(
trpc.model.list.queryOptions(),
);
if (modelsQuery.isPending) {
return <p>در حال دریافت مدلها...</p>;
}
if (modelsQuery.isError) {
return (
<p>
دریافت مدلها انجام نشد.
</p>
);
}
return (
<ul>
{modelsQuery.data.map(
(model) => (
<li key={model.id}>
<strong>
{model.name}
</strong>
<span>
{" "}
{model.category}
</span>
</li>
),
)}
</ul>
);
}
Type مربوط به modelsQuery.data از خروجی Procedure استنباط میشود. نیازی به تعریف دستی Model در Frontend نداریم.
اجرای Query دارای ورودی
const modelQuery = useQuery(
trpc.model.byId.queryOptions({
id: selectedModelId,
}),
);
TypeScript اجازه نمیدهد این ورودی ارسال شود:
trpc.model.byId.queryOptions({
modelIdentifier: 123,
});
زیرا Procedure یک فیلد id از نوع String میخواهد.
ساخت Procedure اتصال به هوش مصنوعی
فایل src/routers/chat.router.ts:
import { z } from "zod";
import {
protectedProcedure,
router,
} from "../trpc.js";
const ChatCompletionSchema = z.object({
id: z.string().optional(),
choices: z
.array(
z.object({
message: z.object({
role: z.string(),
content: z
.string()
.nullable(),
}),
}),
)
.min(1),
usage: z
.object({
prompt_tokens:
z.number().optional(),
completion_tokens:
z.number().optional(),
total_tokens:
z.number().optional(),
})
.optional(),
});
export const chatRouter = router({
create: protectedProcedure
.input(
z.object({
modelId: z.string().min(1),
prompt: z
.string()
.trim()
.min(1)
.max(10_000),
temperature: z
.number()
.min(0)
.max(2)
.default(0.7),
}),
)
.output(
z.object({
content: z.string(),
modelId: z.string(),
usage: z
.object({
inputTokens:
z.number().optional(),
outputTokens:
z.number().optional(),
totalTokens:
z.number().optional(),
})
.optional(),
}),
)
.mutation(
async ({ input, ctx }) => {
const apiKey =
process.env
.DARVAREH_API_KEY;
if (!apiKey) {
throw new Error(
"AI service is not configured",
);
}
const response = await fetch(
"https://api.darvareh.ir/v1/chat/completions",
{
method: "POST",
headers: {
Authorization:
`Bearer ${apiKey}`,
"Content-Type":
"application/json",
"x-request-id":
ctx.requestId,
},
body: JSON.stringify({
model: input.modelId,
temperature:
input.temperature,
messages: [
{
role: "user",
content: input.prompt,
},
],
}),
signal:
AbortSignal.timeout(
60_000,
),
},
);
if (!response.ok) {
throw new Error(
`AI request failed: ${response.status}`,
);
}
const rawData: unknown =
await response.json();
const result =
ChatCompletionSchema.safeParse(
rawData,
);
if (!result.success) {
throw new Error(
"AI response structure is invalid",
);
}
const content =
result.data.choices[0]
.message.content;
if (!content) {
throw new Error(
"AI response is empty",
);
}
return {
content,
modelId: input.modelId,
usage: result.data.usage
? {
inputTokens:
result.data.usage
.prompt_tokens,
outputTokens:
result.data.usage
.completion_tokens,
totalTokens:
result.data.usage
.total_tokens,
}
: undefined,
};
},
),
});
Router جدید را به AppRouter اضافه کنید:
import { router } from "../trpc.js";
import { chatRouter } from "./chat.router.js";
import { modelRouter } from "./model.router.js";
export const appRouter = router({
chat: chatRouter,
model: modelRouter,
});
export type AppRouter =
typeof appRouter;
متغیرهای محیطی:
PORT=3000
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
کلید API باید فقط در Backend نگهداری شود. tRPC باعث Type-Safe شدن ارتباط Frontend و Backend میشود، اما قرار دادن Secret در Frontend را امن نمیکند.
برای دریافت API Key میتوانید در درواره ثبتنام کنید. شناسه مدلها، قابلیتها و قیمتهای بهروز نیز در صفحه مدلهای درواره قرار دارند.
اجرای Mutation هوش مصنوعی در React
فایل src/components/ChatForm.tsx:
import {
useMutation,
} from "@tanstack/react-query";
import {
useState,
type FormEvent,
} from "react";
import { useTRPC } from "../trpc";
export function ChatForm() {
const trpc = useTRPC();
const [prompt, setPrompt] =
useState("");
const [modelId, setModelId] =
useState("YOUR_MODEL_ID");
const chatMutation =
useMutation(
trpc.chat.create.mutationOptions(),
);
function handleSubmit(
event: FormEvent,
) {
event.preventDefault();
if (!prompt.trim()) {
return;
}
chatMutation.mutate({
prompt,
modelId,
temperature: 0.7,
});
}
return (
<section>
<form onSubmit={handleSubmit}>
<label>
شناسه مدل
<input
value={modelId}
onChange={(event) =>
setModelId(
event.target.value,
)
}
/>
</label>
<label>
پیام
<textarea
value={prompt}
onChange={(event) =>
setPrompt(
event.target.value,
)
}
/>
</label>
<button
type="submit"
disabled={
chatMutation.isPending
}
>
{chatMutation.isPending
? "در حال پردازش..."
: "ارسال"}
</button>
</form>
{chatMutation.isError && (
<p>
پردازش درخواست انجام نشد.
</p>
)}
{chatMutation.data && (
<article>
<h2>پاسخ</h2>
<p>
{
chatMutation.data
.content
}
</p>
{chatMutation.data
.usage && (
<small>
مجموع توکنها:{" "}
{chatMutation.data
.usage
.totalTokens ??
"نامشخص"}
</small>
)}
</article>
)}
</section>
);
}
TypeScript ساختار ورودی Mutation و خروجی آن را از chat.create تشخیص میدهد.
اگر Backend نام content را به answer تغییر دهد، محل استفاده قدیمی در Frontend خطای TypeScript خواهد گرفت.
Invalidate کردن Query پس از Mutation
فرض کنید پس از ساخت Conversation باید فهرست مکالمات دوباره دریافت شود:
import {
useMutation,
useQueryClient,
} from "@tanstack/react-query";
import { useTRPC } from "../trpc";
function CreateConversationButton() {
const trpc = useTRPC();
const queryClient =
useQueryClient();
const mutation = useMutation(
trpc.conversation.create
.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries(
trpc.conversation.list
.queryFilter(),
);
},
}),
);
return (
<button
onClick={() =>
mutation.mutate({
title: "مکالمه جدید",
modelId:
"YOUR_MODEL_ID",
})
}
>
ساخت مکالمه
</button>
);
}
این ترکیب مزایای TanStack Query مانند Cache Invalidation و State Management درخواست را با Typeهای tRPC همراه میکند.
مدیریت خطا در tRPC
برای خطاهای شناختهشده از TRPCError استفاده کنید:
import {
TRPCError,
} from "@trpc/server";
throw new TRPCError({
code: "NOT_FOUND",
message:
"مکالمه مورد نظر پیدا نشد.",
});
کدهای رایج:
BAD_REQUESTUNAUTHORIZEDFORBIDDENNOT_FOUNDCONFLICTTOO_MANY_REQUESTSINTERNAL_SERVER_ERRORTIMEOUT
نمونه:
const conversation =
await findConversation(
input.id,
);
if (!conversation) {
throw new TRPCError({
code: "NOT_FOUND",
message:
"مکالمه پیدا نشد.",
});
}
tRPC خطا را به ساختار قابلاستفاده در Client تبدیل میکند. براساس مستندات Error Handling، Response خطا شامل اطلاعاتی مانند Code، HTTP Status و مسیر Procedure است.
در محیط Production نباید Stack Trace و جزئیات داخلی به Client نمایش داده شوند. tRPC بهصورت پیشفرض Stack را فقط در حالت Development قرار میدهد، اما تنظیم محیط استقرار باید صحیح باشد.
سفارشیسازی Error Format
const t = initTRPC
.context<Context>()
.create({
errorFormatter({
shape,
error,
}) {
return {
...shape,
data: {
...shape.data,
validation:
error.cause instanceof
z.ZodError
? z.flattenError(
error.cause,
).fieldErrors
: null,
},
};
},
});
این روش میتواند خطاهای Validation را برای فرم React قابلاستفادهتر کند.
پیامهای فنی و اطلاعات حساس را بدون بررسی به Response عمومی اضافه نکنید.
اتصال tRPC به Prisma
میتوان Prisma Client را در Context قرار داد:
import { prisma } from "./lib/prisma.js";
export async function createContext({
req,
}: CreateExpressContextOptions) {
const user =
await getCurrentUser(req);
return {
prisma,
user,
requestId:
req.header("x-request-id") ??
crypto.randomUUID(),
};
}
Procedure:
const conversationRouter = router({
list: protectedProcedure.query(
async ({ ctx }) => {
return ctx.prisma
.conversation
.findMany({
where: {
userId:
ctx.user.id,
},
select: {
id: true,
title: true,
modelId: true,
updatedAt: true,
},
orderBy: {
updatedAt: "desc",
},
take: 50,
});
},
),
});
حتی اگر Client شناسه کاربر دیگری ارسال کند، Query باید مالکیت داده را با اطلاعات کاربر موجود در Context محدود کند.
تفکیک Routerها در پروژه بزرگ
Router اصلی را به فایلهای کوچک تقسیم کنید:
src/
routers/
user.router.ts
conversation.router.ts
message.router.ts
model.router.ts
chat.router.ts
index.ts
Router اصلی:
export const appRouter = router({
user: userRouter,
conversation:
conversationRouter,
message: messageRouter,
model: modelRouter,
chat: chatRouter,
});
این ساختار خوانایی و تستپذیری پروژه را بهتر میکند.
استخراج Type ورودی و خروجی
گاهی برای Service، Component یا تست به Type یک Procedure نیاز دارید.
در سطح کامل Router:
import type {
inferRouterInputs,
inferRouterOutputs,
} from "@trpc/server";
import type {
AppRouter,
} from "./routers/index.js";
type RouterInputs =
inferRouterInputs<AppRouter>;
type RouterOutputs =
inferRouterOutputs<AppRouter>;
type CreateChatInput =
RouterInputs["chat"]["create"];
type CreateChatOutput =
RouterOutputs["chat"]["create"];
اکنون Typeها بدون تعریف مجدد از Router استخراج میشوند.
در ادغام TanStack Query نیز ابزارهای inferInput و inferOutput وجود دارند. نمونههای آن در راهنمای استفاده از TanStack React Query ارائه شدهاند.
تست Router با createCaller
میتوان Router را بدون اجرای HTTP Server تست کرد:
import {
describe,
expect,
it,
} from "vitest";
import { appRouter } from "./routers/index.js";
describe("model router", () => {
it("returns model list", async () => {
const caller =
appRouter.createCaller({
requestId: "test-request",
user: {
id: "usr_test",
name: "Test User",
},
});
const models =
await caller.model.list();
expect(
models.length,
).toBeGreaterThan(0);
});
});
تست ورودی نامعتبر:
it(
"rejects empty model id",
async () => {
const caller =
appRouter.createCaller({
requestId:
"test-request",
user: null,
});
await expect(
caller.model.byId({
id: "",
}),
).rejects.toThrow();
},
);
createCaller Procedureها و Middlewareها را مستقیماً اجرا میکند. جزئیات در مستندات Server-Side Calls موجود است.
آیا از createCaller داخل Procedure استفاده کنیم؟
معمولاً بهتر است Business Logic را در Service جداگانه قرار دهید و هم Procedureها و هم کد Server آن Service را فراخوانی کنند.
روش نامناسب:
const result =
await caller.chat.create(input);
داخل یک Procedure دیگر ممکن است Middlewareها و Validationها را دوباره اجرا کند.
روش بهتر:
async function createChat(
input: CreateChatInput,
dependencies: Dependencies,
) {
// Business logic
}
سپس Procedure:
create: protectedProcedure
.input(CreateChatInputSchema)
.mutation(({ input, ctx }) => {
return createChat(
input,
{
user: ctx.user,
prisma: ctx.prisma,
},
);
});
Data Transformer
JSON بهصورت مستقیم بعضی Typeها مانند Date، Map، Set و BigInt را به شکل اصلی منتقل نمیکند.
برای Typeهای پیچیده میتوان از Transformer سازگار استفاده کرد. اگر از Transformer استفاده میکنید، باید آن را در Server و Client یکسان تنظیم کنید.
قبل از افزودن Transformer بررسی کنید آیا API واقعاً نیاز دارد Typeهای پیچیده را مستقیماً منتقل کند یا بهتر است آنها را به رشته یا Object ساده تبدیل کند.
Subscription در tRPC
Subscription برای دادههای بلادرنگ کاربرد دارد:
- اعلانها
- وضعیت Job
- پیشرفت تولید ویدئو
- تغییر وضعیت Agent
- رویدادهای زنده
- Streaming داده
در tRPC 11 میتوان از Async Generator و روشهای انتقال مانند SSE یا WebSocket استفاده کرد.
نمونه مفهومی:
const jobRouter = router({
progress:
protectedProcedure
.input(
z.object({
jobId: z.string(),
}),
)
.subscription(
async function* ({
input,
signal,
}) {
while (
!signal?.aborted
) {
const progress =
await getJobProgress(
input.jobId,
);
yield progress;
if (
progress.status ===
"completed" ||
progress.status ===
"failed"
) {
return;
}
await new Promise(
(resolve) =>
setTimeout(
resolve,
1000,
),
);
}
},
),
});
برای بسیاری از کاربردها Polling ساده یا SSE کافی است. قبل از افزودن WebSocket، نیاز واقعی و هزینه نگهداری آن را بررسی کنید.
tRPC و Streaming پاسخ هوش مصنوعی
Streaming Tokenهای مدل هوش مصنوعی با Mutation معمولی که یک Response نهایی برمیگرداند متفاوت است.
گزینههای ممکن:
- Streaming Query یا Mutation در tRPC 11
- Subscription مبتنی بر SSE
- Endpoint اختصاصی Streaming
- WebSocket
- ذخیره Job و Polling وضعیت
اگر API عمومی درواره پاسخ SSE ارائه میدهد، میتوانید Streaming را در Backend دریافت و از طریق یک مسیر Streaming متناسب به Client منتقل کنید.
برای اولین نسخه محصول، پاسخ غیرجریانی سادهتر است. پس از تثبیت مدیریت خطا، لغو درخواست و ثبت مصرف میتوان Streaming را اضافه کرد.
Batching و عملکرد
httpBatchLink میتواند چند عملیات همزمان را در یک HTTP Request قرار دهد:
await Promise.all([
trpc.user.me.query(),
trpc.model.list.query(),
trpc.conversation.list.query(),
]);
مزایا:
- کاهش تعداد Requestهای HTTP
- کاهش بخشی از سربار شبکه
- سادهتر شدن بارگذاری همزمان صفحه
اما باید محدودیتهایی نیز در نظر گرفته شوند:
- Batch بسیار بزرگ میتواند Response سنگین ایجاد کند.
- هر Procedure همچنان عملیات خود را اجرا میکند.
- یک درخواست بزرگ ممکن است زمان بیشتری درگیر بماند.
- Timeout و محدودیت اندازه Request باید کنترل شوند.
نمونه محدودسازی:
httpBatchLink({
url: "/trpc",
maxItems: 10,
maxURLLength: 2_000,
});
جلوگیری از Queryهای سنگین
tRPC بهصورت خودکار Query دیتابیس را بهینه نمیکند. برای عملکرد مناسب:
- خروجی لیستها را صفحهبندی کنید.
- تعداد آیتمهای ورودی را محدود کنید.
- Relationهای بزرگ را بدون محدودیت نخوانید.
- فقط فیلدهای موردنیاز را انتخاب کنید.
- Queryهای دیتابیس را Index کنید.
- Procedureهای کند را اندازهگیری کنید.
- Timeout مناسب برای API خارجی قرار دهید.
- تعداد عملیات Batch را محدود کنید.
- پاسخهای قابل Cache را شناسایی کنید.
طراحی Pagination در tRPC
Schema:
const PaginationSchema = z.object({
cursor: z
.string()
.nullish(),
limit: z
.number()
.int()
.min(1)
.max(100)
.default(20),
});
Procedure:
list: protectedProcedure
.input(PaginationSchema)
.query(async ({ input, ctx }) => {
const items =
await ctx.prisma
.conversation
.findMany({
where: {
userId:
ctx.user.id,
},
take:
input.limit + 1,
cursor: input.cursor
? {
id: input.cursor,
}
: undefined,
skip: input.cursor
? 1
: 0,
orderBy: {
id: "desc",
},
});
let nextCursor:
| string
| undefined;
if (
items.length >
input.limit
) {
const nextItem =
items.pop();
nextCursor =
nextItem?.id;
}
return {
items,
nextCursor,
};
});
در React میتوان این Procedure را با Infinite Query در TanStack Query ترکیب کرد.
نسخهبندی API در tRPC
در یک Monorepo که Client و Server همزمان منتشر میشوند، تغییرات Type توسط Build شناسایی میشوند. اما اگر نسخههای Client و Server جداگانه Deploy شوند، باید سازگاری را مدیریت کنید.
راهکارها:
- تغییرات Breaking را تدریجی اعمال کنید.
- ابتدا فیلد جدید را اضافه و اختیاری نگه دارید.
- Clientها را بهروزرسانی کنید.
- سپس فیلد قدیمی را حذف کنید.
- Router نسخهبندیشده بسازید:
const appRouter = router({
v1: v1Router,
v2: v2Router,
});
- نسخه Client و Server را در Deployment هماهنگ کنید.
- Contract Test داشته باشید.
Type Safety فقط زمانی مفید است که نسخه Client و Server با قرارداد سازگار اجرا شوند.
tRPC در Monorepo
یکی از بهترین محیطها برای tRPC، Monorepo است:
apps/
api/
web/
packages/
api-contract/
database/
shared/
Type مربوط به AppRouter میتواند از Package داخلی صادر شود:
export type {
AppRouter,
} from "./routers/index";
Client فقط Type را وارد میکند:
import type {
AppRouter,
} from "@project/api-contract";
مراقب باشید Package قرارداد، کد Server، Secretها، اتصال دیتابیس یا وابستگیهای Node.js را وارد Bundle مرورگر نکند.
tRPC برای API عمومی مناسب است؟
اگر فقط یک Frontend TypeScript تحت کنترل خودتان دارید، tRPC انتخاب بسیار خوبی است.
اگر API باید توسط موارد زیر استفاده شود، بهتر است قرارداد عمومی دیگری نیز داشته باشید:
- اپلیکیشن Python
- مشتری PHP یا Laravel
- اپلیکیشن Android با Kotlin
- برنامه iOS با Swift
- شرکای تجاری
- کاربران خارجی
- ابزارهایی مانند Postman
- سرویسهای Third-Party
در این حالت میتوان:
- API عمومی را REST و OpenAPI نگه داشت.
- داشبورد داخلی را با tRPC ساخت.
- Business Logic مشترک را میان هر دو Adapter استفاده کرد.
برای درواره نیز API عمومی مناسب توسعهدهندگان باید قرارداد استاندارد و مستقل داشته باشد؛ درحالیکه tRPC میتواند برای ارتباط داخلی Dashboard TypeScript با Backend استفاده شود.
اشتباهات رایج در tRPC
وارد کردن AppRouter بهصورت Runtime در Client
اشتباه:
import {
AppRouter,
} from "../server";
درست:
import type {
AppRouter,
} from "../server";
تصور اینکه TypeScript جای Validation را میگیرد
داده HTTP در Runtime دریافت میشود و باید با Zod یا Validator دیگر بررسی شود.
قرار دادن Secret در Frontend
tRPC کلید API را مخفی نمیکند. اتصال به سرویس هوش مصنوعی باید در Procedure سمت Server انجام شود.
استفاده از publicProcedure برای تمام عملیات
Procedureهای مربوط به داده کاربر باید Context و مجوز مناسب داشته باشند.
اعتماد به شناسه userId ارسالشده از Client
شناسه کاربر باید از Session معتبر در Context گرفته شود، نه از ورودی قابل تغییر Client.
قراردادن تمام Business Logic در Router
Router باید ورودی، Context و خروجی را مدیریت کند. منطق اصلی بهتر است در Serviceهای تستپذیر قرار گیرد.
ساخت Router بسیار بزرگ
Routerها را براساس Domain تقسیم کنید:
- user
- billing
- conversation
- message
- model
- chat
نادیده گرفتن Pagination
هیچ Procedure لیستی نباید بدون محدودیت، دادهای با رشد نامحدود برگرداند.
نمایش خطای داخلی
پیام دیتابیس، Stack Trace و جزئیات API خارجی نباید مستقیماً به کاربر نمایش داده شوند.
استفاده از tRPC برای تمام Clientهای خارجی
tRPC برای اکوسیستم TypeScript عالی است، اما برای مصرفکنندگان چندزبانه همیشه انتخاب مناسبی نیست.
نبود تست Runtime
موفق بودن Type Check جای تست Integration، تست دیتابیس و تست رفتار Procedure را نمیگیرد.
چکلیست tRPC برای Production
- نسخههای
@trpc/serverو@trpc/clientهماهنگ هستند. - TypeScript با
strictفعال شده است. AppRouterفقط باimport typeوارد Client میشود.- تمام Procedureهای دارای ورودی، Runtime Validator دارند.
- Procedureهای خصوصی از Context معتبر استفاده میکنند.
- مجوز دسترسی در Backend بررسی میشود.
- شناسه کاربر از Session گرفته میشود، نه Request Body.
- Secretها فقط در Backend نگهداری میشوند.
- خطاهای داخلی به Client نمایش داده نمیشوند.
- Routerها براساس Domain تقسیم شدهاند.
- Business Logic در Serviceهای تستپذیر قرار دارد.
- فهرستها Pagination دارند.
- Batch Size محدود شده است.
- Timeout درخواستهای خارجی تعیین شده است.
- پاسخ API خارجی اعتبارسنجی میشود.
- Queryهای دیتابیس اندازهگیری و بهینه شدهاند.
- Procedureها با
createCallerو Integration Test بررسی میشوند. - نسخه Client و Server هنگام Deployment سازگار است.
- برای API عمومی چندزبانه، REST یا قرارداد مستقل وجود دارد.
- Logging شامل Request ID و زمان اجرا است.
- داده حساس و متن خصوصی کاربران بدون سیاست مشخص Log نمیشود.
پرسشهای متداول
tRPC چیست؟
tRPC فریمورکی برای ساخت APIهای End-to-End Type-Safe میان Backend و Frontend TypeScript است. Client نوع Procedureها، ورودیها و خروجیها را مستقیماً از Router سمت Server دریافت میکند.
آیا tRPC جایگزین REST است؟
در بعضی برنامههای Full-Stack TypeScript میتواند جایگزین REST داخلی شود، اما برای API عمومی، چندزبانه و قابل استفاده توسط مشتریان خارجی، REST و OpenAPI همچنان انتخاب مناسبی هستند.
آیا tRPC جایگزین GraphQL است؟
tRPC و GraphQL مسائل مشابهی را با روشهای متفاوت حل میکنند. GraphQL دارای Schema مستقل و امکان انتخاب فیلد توسط Client است؛ tRPC بر Type Inference مستقیم در پروژه TypeScript تمرکز دارد.
آیا tRPC فقط با React کار میکند؟
خیر. Client استاندارد tRPC را میتوان بدون React استفاده کرد. ادغام رسمی با TanStack React Query تجربه توسعه React را بهتر میکند.
آیا tRPC به Zod نیاز دارد؟
اجباری نیست، اما tRPC استفاده از Validator را توصیه میکند و Zod یکی از انتخابهای اصلی آن است. بدون Validation، داده Runtime همچنان ممکن است نامعتبر باشد.
آیا tRPC به Code Generation نیاز دارد؟
معمولاً خیر. Client Type مربوط به AppRouter را مستقیماً استفاده میکند.
آیا tRPC از Express پشتیبانی میکند؟
بله. Adapter رسمی Express از مسیر @trpc/server/adapters/express در دسترس است.
آیا tRPC با Prisma کار میکند؟
بله. میتوان Prisma Client را در Context قرار داد و در Procedureها Queryهای Type-Safe اجرا کرد.
آیا میتوان با tRPC API هوش مصنوعی ساخت؟
بله. Procedure سمت Server میتواند درخواست را به API هوش مصنوعی ارسال کند و نتیجه Type-Safe را به React برگرداند. کلید API باید در Backend باقی بماند.
آیا tRPC از Streaming پشتیبانی میکند؟
نسخههای جدید tRPC از Streaming و Subscription از طریق روشهایی مانند SSE و WebSocket پشتیبانی میکنند. انتخاب روش باید براساس نیاز برنامه انجام شود.
آیا tRPC برای Microservices مناسب است؟
برای ارتباط Frontend و Backend TypeScript یا سرویسهای تحت کنترل یک تیم مناسب است. برای مرز عمومی میان سرویسهای مستقل، قراردادهای استاندارد مانند HTTP/OpenAPI، GraphQL، gRPC یا پیامرسانی رویدادمحور ممکن است مناسبتر باشند.
آیا Type Safety مانع تمام خطاهای API میشود؟
خیر. Type Safety خطاهای قراردادی زیادی را کاهش میدهد، اما جایگزین Runtime Validation، تست، مدیریت خطا، مجوز دسترسی، Monitoring و بررسی منطق برنامه نیست.
جمعبندی
tRPC راهی ساده و قدرتمند برای ساخت API کاملاً Type-Safe در پروژههایی است که Frontend و Backend هر دو با TypeScript توسعه داده میشوند.
با تعریف یک AppRouter در Backend، Client میتواند بدون Typeهای تکراری یا Code Generation، Procedureها و ورودی و خروجی آنها را بشناسد. ترکیب tRPC با Zod، React، TanStack Query، Prisma و Node.js یک Stack منسجم برای ساخت برنامههای Full-Stack ایجاد میکند.
بهترین کاربرد tRPC معمولاً در شرایط زیر است:
- Frontend و Backend هر دو TypeScript هستند.
- کدها در Monorepo یا مخزنهای هماهنگ نگهداری میشوند.
- Client و Server تحت کنترل یک تیم هستند.
- سرعت توسعه و Refactoring اهمیت دارد.
- API عمومی چندزبانه هدف اصلی نیست.
برای برنامههای هوش مصنوعی نیز میتوان Procedureهای tRPC را بهعنوان لایه امن میان React و API درواره استفاده کرد. Frontend فقط Procedure Type-Safe را فراخوانی میکند، درحالیکه کلید API، اعتبارسنجی پاسخ، Timeout و مدیریت خطا در Backend باقی میمانند.
اگر محصول شما علاوه بر Dashboard داخلی، API عمومی برای توسعهدهندگان مختلف ارائه میکند، استفاده ترکیبی منطقیتر است: tRPC برای رابط داخلی TypeScript و REST یا OpenAPI برای قرارداد عمومی.
منابع تکمیلی
- مستندات رسمی tRPC
- راهنمای سریع tRPC
- تعریف Procedure در tRPC
- اعتبارسنجی ورودی و خروجی
- Authorization در tRPC
- مدیریت خطا در tRPC
- راهنمای HTTP Batch Link
- اتصال tRPC به TanStack React Query
- راهنمای استفاده از tRPC React Query
- مهاجرت از tRPC 10 به tRPC 11
مقالات مرتبط
- هوش مصنوعی با Node.js؛ ساخت اپلیکیشن AI با Express و API
- ساخت چتبات هوش مصنوعی با Next.js، React و API درواره
- آموزش کامل REST API و طراحی RESTful
- ساخت GraphQL Query با هوش مصنوعی
- ساخت SDK و API Client از OpenAPI با هوش مصنوعی
- Structured Outputs و JSON Schema در API هوش مصنوعی
- ساخت API هوش مصنوعی آماده Production
- آموزش کامل JSON در Python، JavaScript و API هوش مصنوعی
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.