---
title: 'tRPC v11 أمان الأنواع من الطرف للطرف: Server +'
description: 'ابنِ API tRPC v11 آمن تماماً من حيث الأنواع في TypeScript: سيرفر مستقل، وعميل vanilla، والتحقق من البيانات باستخدام Zod، ومعالجة الأخطاء. بدون إطار عمل، وكل شيء قابل للتشغيل.'
url: https://nerdleveltech.com/ar/trpc-v11-end-to-end-type-safety-server-client-tutorial
language: ar
date: '2026-06-22'
category: backend
tags:
  - trpc
  - trpc v11
  - typescript
  - type safety
  - zod
  - node
  - api
  - backend
markdown_url: https://nerdleveltech.com/ar/trpc-v11-end-to-end-type-safety-server-client-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/trpc-v11-end-to-end-type-safety-server-client-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# tRPC v11 أمان الأنواع من الطرف للطرف: Server +

تمنح tRPC عميل TypeScript استنتاجاً كاملاً للأنواع (type inference) عبر إجراءات الخادم بدون توليد كود (code generation) وبدون مخطط API منفصل. يبني هذا البرنامج التعليمي API كاملاً وقابلاً للتشغيل باستخدام tRPC v11 — خادم Node مستقل وعميل vanilla — بدون Next.js أو أي إطار عمل.

## ملخص

ستقوم ببناء API صغير لـ "الملاحظات" من مجلد فارغ باستخدام tRPC v11.18.0. ستحدد راوتر (router) مع استعلامين (queries) وطفرة (mutation)، وتتحقق من المدخلات باستخدام Zod، وتخدمها باستخدام المحول المستقل (غلاف رفيع فوق خادم `http` الخاص بـ Node)، ثم تستدعيها من عميل vanilla مكتوب بالكامل حيث يعرف المحرر كل نوع إدخال وإرجاع. ستضيف معالجة `TRPCError`، وتفحص تنسيق HTTP الخام باستخدام `curl`، وتصلح خطأ إعداد شائعاً جداً (عدم تطابق بين `basePath` و `url`). تم التحقق من كل أمر وكتلة كود من البداية إلى النهاية — `tsc --noEmit`، والخادم، والعميل، و `curl` — في 22 يونيو 2026.

## ما ستتعلمه

- كيفية هيكلة مشروع Node + TypeScript يقوم بتشغيل ملفات `.ts` مباشرة
- كيفية تعريف راوتر tRPC مع استعلامات وطفرة
- كيفية التحقق من صحة مدخلات الإجراء باستخدام Zod
- كيفية خدمة الراوتر باستخدام المحول المستقل وتمكين CORS
- كيفية بناء عميل vanilla تأتي أنواعه مباشرة من الخادم
- كيفية رمي ومعالجة الأخطاء المكتوبة باستخدام `TRPCError`
- كيفية التحقق من API عبر HTTP الخام باستخدام `curl`

## المتطلبات الأساسية

- **Node 20 أو أحدث** — أي إصدار LTS مدعوم سيعمل. تم التحقق هنا على Node 22.22.3. (لا تعلن tRPC v11 عن محرك Node محدد، لكن المحول المستقل يغلف خادم HTTP المدمج في Node، لذا فإن إصدار LTS الحالي هو الأساس الآمن.)
- **TypeScript 5.7.2 أو أحدث** — هذا هو متطلب tRPC v11.[^4] لقد ثبتنا الإصدار 6.0.3.
- إلمام أساسي بـ `async/await` والطرفية (terminal).

ملاحظة سريعة حول الإصدارات: تم إطلاق tRPC v11 كإصدار مستقر في **21 مارس 2025**[^1] بعد فترة طويلة تحت وسم `@next`، لذا فإن API في هذا الدليل مستقر وليس تجريبياً.

## الخطوة 1 — هيكلة المشروع

أنشئ مجلداً، وقم بتهيئة حزمة ESM، وثبت التبعيات المحددة.

```bash
mkdir trpc-notes && cd trpc-notes
npm init -y
npm pkg set type=module

# runtime deps
npm install @trpc/server@11.18.0 @trpc/client@11.18.0 zod@4.4.3 cors@2.8.6

# dev deps: TypeScript, a .ts runner, and types
npm install -D TypeScript@6.0.3 tsx@4.22.4 @types/node@26.0.0 @types/cors@2.8.19
```

يسمح لنا `tsx` بتشغيل ملفات TypeScript مباشرة (بدون خطوة بناء منفصلة أثناء التطوير)، و `cors` مطلوب فقط لأن الخادم المستقل لا يضبط ترويسات CORS من تلقاء نفسه.[^2]

أنشئ ملف `tsconfig.json` مهيأً لفحص الأنواع (وليس لإصدار الكود — حيث يتولى `tsx` التنفيذ):

```json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "preserve",
    "moduleResolution": "bundler",
    "strict": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src"]
}
```

تجبرك خاصية `verbatimModuleSyntax` على استخدام `import type` لاستيراد الأنواع فقط — وهو أمر يهم كثيراً بعد قليل، لأن استيراد *نوع* الراوتر الخاص بك هو الحيلة الكاملة التي تبقي كود الخادم بعيداً عن حزمة العميل (client bundle).

## الخطوة 2 — تعريف راوتر tRPC مع استعلامات وطفرة

هذا هو جوهر tRPC. أنشئ `src/router.ts`. ستقوم بتهيئة tRPC مرة واحدة، وتصدير مساعدين قابلين لإعادة الاستخدام، ثم تعلن عن الإجراءات. الـ `.query()` مخصص للقراءة، والـ `.mutation()` مخصص للكتابة.

```ts
// src/router.ts
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;

interface Note {
  id: string;
  title: string;
  body: string;
  createdAt: string;
}

// In a real app this would be your database. Keep it in memory for the demo.
const notes: Note[] = [
  { id: '1', title: 'Welcome', body: 'Your first note.', createdAt: new Date().toISOString() },
];

export const appRouter = router({
  // a query with no input
  list: publicProcedure.query(() => notes),

  // a query with a validated string input
  byId: publicProcedure.input(z.string()).query(({ input }) => {
    const note = notes.find((n) => n.id === input);
    if (!note) {
      throw new TRPCError({ code: 'NOT_FOUND', message: `No note with id ${input}` });
    }
    return note;
  }),

  // a mutation with a validated object input
  create: publicProcedure
    .input(z.object({ title: z.string().min(1), body: z.string().min(1) }))
    .mutation(({ input }) => {
      const note: Note = {
        id: String(notes.length + 1),
        title: input.title,
        body: input.body,
        createdAt: new Date().toISOString(),
      };
      notes.push(note);
      return note;
    }),
});

// Export ONLY the type. This is what the client imports.
export type AppRouter = typeof appRouter;
```

السطر الأخير هو الذي يمنح tRPC شعارها المميز. `AppRouter` هو نوع TypeScript نقي يصف كل إجراء، ومدخلاته، وقيمة إرجاعه. يستورد العميل هذا النوع ولا شيء غيره — لا يعبر أي كود خادم وقت التشغيل الحدود.

## الخطوة 3 — التحقق من صحة المدخلات باستخدام Zod

لقد قمت بذلك بالفعل في الخطوة 2، ويستحق الأمر التمهل عنده لأنه يؤدي وظيفة مزدوجة. تأخذ كل مكالمة `.input()` مدققاً (validator). في وقت التشغيل، تقوم tRPC بتحليل الطلب الوارد مقابل المخطط وترفض أي شيء لا يتطابق قبل تشغيل المعالج الخاص بك. في وقت الترجمة (compile time)، يصبح النوع المستنتج من المخطط هو نوع `input` داخل المعالج — والنوع الذي يضطر العميل لتمريره.

لذا فإن `z.object({ title: z.string().min(1), body: z.string().min(1) })` يعني أن استدعاء العميل لـ `create` مع عنوان مفقود، أو رقم حيث يجب أن يكون هناك نص، هو خطأ TypeScript في المحرر الخاص بك *و* خطأ تحقق في وقت التشغيل. تقبل tRPC v11 أي مدقق ينفذ مواصفات Standard Schema، لذا فإن Zod و Valibot و ArkType جميعها تعمل بنفس الطريقة.[^5] نحن نستخدم Zod 4 هنا.

## الخطوة 4 — خدمتها باستخدام المحول المستقل

المحول المستقل هو أبسط طريقة لتشغيل راوتر tRPC: فهو غلاف رفيع حول خادم HTTP المدمج في Node.[^2] أنشئ `src/server.ts`:

```ts
// src/server.ts
import { createHTTPServer } from '@trpc/server/adapters/standalone';
import cors from 'cors';
import { appRouter } from './router';

const server = createHTTPServer({
  router: appRouter,
  middleware: cors(), // allow browser clients during development
  basePath: '/trpc/', // requests are served under /trpc/*
  createContext() {
    return {};
  },
});

server.listen(3000);
console.log('tRPC server on http://localhost:3000/trpc');
```

هناك تفصيلان يقع فيهما الناس عادةً. أولاً، يتم تمرير `cors()` كـ `middleware` لأن الخادم المستقل لا يضبط **أي** ترويسات CORS افتراضياً — بدونها، ستفشل عملاء المتصفح بخطأ CORS (لا تتأثر استدعاءات خادم إلى خادم و `curl`).[^2] ثانياً، `basePath: '/trpc/'` تعني أن كل إجراء يعيش تحت `/trpc/` — يصبح `list` هو `http://localhost:3000/trpc/list`. الـ `basePath` الافتراضي هو `'/'`؛ لقد قمنا بضبطه صراحةً ليتطابق مع رابط العميل في الخطوة التالية. تذكر هذا — فهو الخطأ المذكور في قسم استكشاف الأخطاء وإصلاحها.

ابدأ تشغيله في طرفية واحدة:

```bash
npx tsx src/server.ts
# tRPC server on http://localhost:3000/trpc
```

## الخطوة 5 — بناء عميل vanilla تأتي أنواعه من الخادم

أنشئ `src/client.ts`. يستورد العميل **نوع** `AppRouter` (لاحظ `import type`) ويمرره إلى `createTRPCClient`. هذا النوع العام (generic) الوحيد هو ما يجعل العميل بأكمله آمناً من حيث الأنواع.

```ts
// src/client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './router';

const trpc = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});

async function main() {
  // `query` for reads, `mutate` for writes — both fully typed
  console.log('list ->', await trpc.list.query());

  const created = await trpc.create.mutate({ title: 'Buy milk', body: '2 liters' });
  console.log('create ->', created);

  console.log('byId ->', await trpc.byId.query(created.id));
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
```

`createTRPCClient` هو الاسم في الإصدار v11. إذا كنت تتبع برنامجاً تعليمياً أقدم يستخدم `createTRPCProxyClient`، فإن هذه الوظيفة لا تزال موجودة ولكنها مهجورة — حيث أسقط الإصدار v11 كلمة "Proxy" من عميل API العام.[^4] يقوم رابط `httpBatchLink` النهائي بتجميع مكالمات متزامنة متعددة في طلب HTTP واحد تلقائياً.[^7]

قم بتشغيله في طرفية ثانية (اترك الخادم قيد التشغيل):

```bash
npx tsx src/client.ts
```

سترى المخرجات الموضحة أدناه — يعيد `byId` الملاحظة التي أنشأتها للتو، لأن المخزن في الذاكرة يستمر طوال فترة تشغيل عملية الخادم:

```text
list -> [
  { id: '1', title: 'Welcome', body: 'Your first note.', createdAt: '2026-06-22T03:15:07.507Z' }
]
create -> { id: '2', title: 'Buy milk', body: '2 liters', createdAt: '2026-06-22T03:15:09.537Z' }
byId -> { id: '2', title: 'Buy milk', body: '2 liters', createdAt: '2026-06-22T03:15:09.537Z' }
```

الفائدة الكبرى: مرر الفأرة فوق `created` في المحرر الخاص بك وستجده من النوع `{ id: string; title: string; body: string; createdAt: string }` — مستنتج مباشرة من معالج الخادم، بدون واجهة مشتركة، ولا مستند OpenAPI، ولا خطوة توليد كود. قم بتغيير شكل الإرجاع في الخادم وسيتوقف العميل عن الترجمة فوراً. هذا هو معنى "أمان الأنواع من البداية إلى النهاية" في الممارسة العملية.

## الخطوة 6 — معالجة الأخطاء باستخدام TRPCError

لقد قمت بالفعل برمي `TRPCError` في `byId`. يقوم tRPC بربط `code` الخاص بالخطأ بحالة HTTP تلقائيًا، لذا فإن `NOT_FOUND` تتحول إلى `404` دون أن تلمس أكواد الحالة بنفسك.[^6] أضف هذا إلى `main()` الخاص بالعميل لرؤيته يظهر كرفض حقيقي:

```ts
try {
  await trpc.byId.query('999'); // no such note
} catch (err) {
  // err is a TRPCClientError; err.data.code is 'NOT_FOUND', err.data.httpStatus is 404
console.error('expected error ->', (err as Error).message);
}
```

على جانب العميل، القيمة المرمية هي `TRPCClientError` تحمل رسالة الخادم بالإضافة إلى كائن `data` يحتوي على `code` و `httpStatus`. استخدم أكواد الخطأ الموثقة (`BAD_REQUEST`، `UNAUTHORIZED`، `FORBIDDEN`، `NOT_FOUND`، `CONFLICT`، `INTERNAL_SERVER_ERROR`، وغيرها) بدلاً من اختراع أكوادك الخاصة — فكل منها يرتبط بحالة HTTP محددة.[^6] ملاحظة للإنتاج: في بيئة التطوير، تتضمن حمولة الخطأ تتبع `stack` كاملاً؛ بينما يتجاهل tRPC المكدس تلقائيًا عندما يكون `NODE_ENV` هو `production`.[^6]

## التحقق — تأكد من أنه يعمل عبر HTTP الخام

إن tRPC هو "مجرد HTTP"، ويمكنك إثبات ذلك بدون العميل على الإطلاق. أعد تشغيل الخادم أولاً حتى يحتوي المخزن في الذاكرة على الملاحظة الأولية فقط، ثم اختبره باستخدام `curl`. تُرسل الاستعلامات (Queries) كـ `GET` والتحويلات (Mutations) كـ `POST`؛ يقوم `httpBatchLink` بتغليف كل شيء في غلاف دفعي (batch envelope)، لذا يتم تحديد المدخلات بواسطة الفهرس (`"0"`) وتعود الاستجابات كمصفوفة.

```bash
# a query (GET): list all notes
curl 'http://localhost:3000/trpc/list?batch=1&input=%7B%7D'
# [{"result":{"data":[{"id":"1","title":"Welcome", ... }]}}]

# a query with input (GET): byId('1') — input is {"0":"1"} url-encoded
curl 'http://localhost:3000/trpc/byId?batch=1&input=%7B%220%22%3A%221%22%7D'
# [{"result":{"data":{"id":"1","title":"Welcome", ... }}}]

# a mutation (POST): create — body is {"0":{...}}; on a fresh server this is the 2nd note
curl -X POST 'http://localhost:3000/trpc/create?batch=1' \
  -H 'content-type: application/json' \
  -d '{"0":{"title":"From curl","body":"hello"}}'
# [{"result":{"data":{"id":"2","title":"From curl", ... }}}]
```

الملاحظة المفقودة تعيد غلاف خطأ محدد النوع، مع ضبط حالة HTTP على 404:

```bash
curl 'http://localhost:3000/trpc/byId?batch=1&input=%7B%220%22%3A%22999%22%7D'
# [{"error":{"message":"No note with id 999","code":-32004,
#   "data":{"code":"NOT_FOUND","httpStatus":404,"path":"byId", ... }}}]
```

الكود `-32004` هو كود خطأ JSON-RPC الذي يستخدمه tRPC لـ `NOT_FOUND`؛ أما `data.code` و `data.httpStatus` المقروءان بشريًا فهما ما تبرمج بناءً عليه فعليًا.

أخيرًا، قم بفحص الأنواع (type-check) للمشروع بأكمله للتأكد من عدم وجود أي شيء غير محدد النوع بدقة:

```bash
npx tsc --noEmit
# (no output = success)
```

## الأخطاء الشائعة

- **كل استدعاء للعميل يعيد 404، أو `Unable to transform response from server`.** هناك تعارض بين `basePath` الخاص بالخادم و `url` الخاص بالعميل. إذا ضبط الخادم `basePath: '/trpc/'`، يجب أن يكون `url` الخاص بالعميل هو `http://localhost:3000/trpc` (وليس المضيف المجرد). هذا التعارض هو خطأ شائع جدًا في التشغيل الأول لأن مثال الخادم المستقل الرسمي يفترض افتراضيًا أن `basePath` هو `'/'` بينما يشير مثال العميل إلى `/trpc` — اختر واحدًا وحافظ على مزامنتهما.[^2][^3]
- **يفشل عميل المتصفح مع خطأ CORS، لكن `curl` يعمل.** لقد نسيت `middleware: cors()` على الخادم المستقل. تتجاهل `curl` واستدعاءات الخادم إلى الخادم CORS؛ لكن المتصفحات لا تفعل ذلك.[^2]
- **تحذير `createTRPCProxyClient is deprecated`.** قم بتغيير اسمه إلى `createTRPCClient` — نفس الخيارات، اسم جديد في الإصدار v11.[^4]
- **خطأ `tsc`: تبعية نظيرة / TypeScript غير مدعوم.** يتطلب tRPC v11 إصدار TypeScript ≥ 5.7.2.[^4] تحقق من `npx tsc --version`.
- **`The transformer property has moved`.** في الإصدار v11، يتم تكوين محول البيانات (مثل superjson) على الرابط (`httpBatchLink({ url, transformer })`)، وليس في جذر العميل كما في الإصدار v10.[^4]

## الخطوات التالية

لديك الآن API آمن النوع بالكامل مع خادم وعميل وتحقق من الصحة ومعالجة الأخطاء — وقد رأيت أنه يعتمد على HTTP العادي في جوهره. من هنا:

- استبدل المصفوفة الموجودة في الذاكرة بقاعدة بيانات حقيقية. يوضح دليل [Drizzle ORM + pg-boss Atomic Transactions Tutorial](https://nerdleveltech.com/drizzle-orm-pg-boss-atomic-transactions-tutorial) طبقة بيانات محددة النوع يمكن لتحويلاتك استدعاؤها.
- اعتمد بشكل أكبر على أنماط التحقق القائمة على المخطط (schema-first)، بنفس الطريقة التي يدفع بها دليل [TanStack Router Type-Safe Search Params With Zod](https://nerdleveltech.com/tanstack-router-type-safe-search-params-zod-tutorial) مكتبة Zod إلى أطراف التطبيق.
- سرّع فحص الأنواع على موجه (router) متنامٍ باستخدام المترجم الأصلي المذكور في [TypeScript 7 (tsgo): 10x Faster Compiler](https://nerdleveltech.com/TypeScript-7-native-compiler-tsgo).

للإنتاج، أضف `createContext` موثقًا، واستبدل المحول المستقل بمحول Express أو Fastify أو Fetch الذي يناسب مضيفك، وفكر في تكامل TanStack React Query عند ربط هذا بواجهة أمامية.

[^4]: وثائق tRPC، "Migrate from v10 to v11" — `createTRPCProxyClient` ← `createTRPCClient`، transformer-on-link، TypeScript ≥ 5.7.2. <https://trpc.io/docs/migrate-from-v10-to-v11>

[^1]: tRPC، "Announcing tRPC v11" (21 مارس 2025). <https://trpc.io/blog/announcing-trpc-v11>

[^2]: وثائق tRPC، "Standalone Adapter" (v11)، بما في ذلك سلوك `createHTTPServer`، و `basePath`، و CORS/`middleware`. <https://trpc.io/docs/server/adapters/standalone>

[^5]: وثائق tRPC، "Input & Output Validators" — دعم Standard Schema (Zod، Valibot، ArkType). <https://trpc.io/docs/server/validators>

[^7]: وثائق tRPC، "HTTP Batch Link" — تجميع استدعاءات متعددة في طلب واحد. <https://trpc.io/docs/client/links/httpBatchLink>

[^6]: وثائق tRPC، "Error Handling" — أكواد `TRPCError` وتعيينات حالات HTTP الخاصة بها. <https://trpc.io/docs/server/error-handling>

[^3]: وثائق tRPC، "Set up a tRPC Client" (v11) — استخدام `createTRPCClient`، و `httpBatchLink`، و `client.x.query()/.mutate()`. <https://trpc.io/docs/client/vanilla/setup>
