بناء واجهات برمجة تطبيقات آمنة الأنواع من البداية للنهاية مع tRPC و Next.js App Router

أمان الأنواع من البداية للنهاية بدون توليد أكواد. يتيح لك tRPC استدعاء دوال الخادم من العميل مع إكمال تلقائي كامل لـ TypeScript، والتحقق من الصحة، ومعالجة الأخطاء — بدون مخططات REST، بدون محللات GraphQL، بدون مواصفات OpenAPI. في هذا الدليل، ستبني مدير مهام كامل باستخدام Next.js 15 App Router و tRPC.
ما ستتعلمه
بنهاية هذا الدليل، ستكون قادراً على:
- إعداد tRPC v11 مع Next.js 15 App Router باستخدام محول fetch
- تعريف الاستعلامات والتعديلات والاشتراكات مع التحقق بواسطة Zod
- إنشاء وسيط (middleware) للمصادقة والتسجيل
- دمج TanStack React Query v5 لجلب البيانات من جانب العميل
- استخدام المستدعيات من جانب الخادم في Server Components
- معالجة الأخطاء بنظام الأخطاء المدمج في tRPC
- بناء مدير مهام يعمل بالكامل مع عمليات CRUD كاملة
المتطلبات الأساسية
قبل البدء، تأكد من توفر:
- Node.js 20+ مثبت (
node --version) - خبرة في TypeScript (الأنواع، الأنواع العامة، الاستنتاج)
- إلمام بـ Next.js App Router (Server Components، معالجات المسارات)
- أساسيات React Query (اختياري، سنغطي ما تحتاجه)
- محرر أكواد — يُنصح بـ VS Code أو Cursor
لماذا tRPC؟
إذا كان الواجهة الأمامية والخلفية كلاهما بلغة TypeScript، فأنتم بالفعل تتشاركون نظام أنواع واحد. فلماذا تكتب نقاط نهاية REST بأنواع طلب/استجابة منفصلة، أو تحافظ على مخطط GraphQL؟ يقضي tRPC على هذا التكرار بالكامل.
| الميزة | REST | GraphQL | tRPC |
|---|---|---|---|
| أمان الأنواع | يدوي | توليد أكواد | تلقائي |
| تعريف المخطط | OpenAPI | SDL | غير مطلوب |
| منحنى التعلم | منخفض | متوسط | منخفض |
| حجم الحزمة | متغير | ثقيل | أدنى حد |
| الأفضل لـ | APIs عامة | رسوم بيانية معقدة | تطبيقات TS إلى TS |
يتألق tRPC عندما يتشارك العميل والخادم نفس قاعدة أكواد TypeScript — وهذا بالضبط ما يوفره لك Next.js.
الخطوة 1: إنشاء مشروع Next.js
ابدأ بإنشاء هيكل مشروع Next.js 15 جديد:
npx create-next-app@latest trpc-task-manager --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd trpc-task-managerالخطوة 2: تثبيت tRPC والتبعيات
ثبّت حزم tRPC مع TanStack React Query و Zod:
npm install @trpc/server @trpc/client @trpc/react-query @tanstack/react-query zodإليك ما تفعله كل حزمة:
@trpc/server— يعرّف موجه API والإجراءات والوسيط@trpc/client— عميل TypeScript بسيط لاستدعاء API@trpc/react-query— خطافات React تغلف TanStack React Query@tanstack/react-query— إدارة حالة غير متزامنة قوية لـ Reactzod— التحقق من المخطط وقت التشغيل واستنتاج أنواع TypeScript
الخطوة 3: تهيئة الواجهة الخلفية لـ tRPC
أنشئ ملف تهيئة tRPC. هنا تعرّف السياق وبناة الإجراءات.
أنشئ src/trpc/init.ts:
import { initTRPC, TRPCError } from "@trpc/server";
import { ZodError } from "zod";
// Define the context available to all procedures
export type Context = {
userId: string | null;
};
// Create context for each request
export const createTRPCContext = async (): Promise<Context> => {
// In a real app, extract user from session/JWT here
return {
userId: null,
};
};
// Initialize tRPC — this should only be done once
const t = initTRPC.context<Context>().create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});
// Export reusable helpers
export const router = t.router;
export const publicProcedure = t.procedure;
export const createCallerFactory = t.createCallerFactory;
// Middleware: require authentication
const enforceAuth = t.middleware(({ ctx, next }) => {
if (!ctx.userId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You must be logged in to perform this action",
});
}
return next({
ctx: {
userId: ctx.userId,
},
});
});
export const protectedProcedure = t.procedure.use(enforceAuth);يقوم هذا الملف بإعداد ثلاثة عناصر مهمة:
- السياق — البيانات المتاحة لكل إجراء (مثل المستخدم الحالي)
- الإجراءات العامة — يمكن الوصول إليها بدون مصادقة
- الإجراءات المحمية — تتطلب مستخدماً مسجل الدخول
الخطوة 4: تعريف موجه المهام
الآن أنشئ منطق API الفعلي. أنشئ src/trpc/routers/task.ts:
import { z } from "zod";
import { router, publicProcedure, protectedProcedure } from "../init";
import { TRPCError } from "@trpc/server";
// In-memory store (replace with a real database in production)
interface Task {
id: string;
title: string;
description: string;
completed: boolean;
createdAt: Date;
updatedAt: Date;
}
const tasks: Task[] = [
{
id: "1",
title: "Learn tRPC",
description: "Build a type-safe API with tRPC and Next.js",
completed: false,
createdAt: new Date(),
updatedAt: new Date(),
},
];
// Input validation schemas
const createTaskSchema = z.object({
title: z.string().min(1, "Title is required").max(100),
description: z.string().max(500).default(""),
});
const updateTaskSchema = z.object({
id: z.string(),
title: z.string().min(1).max(100).optional(),
description: z.string().max(500).optional(),
completed: z.boolean().optional(),
});
export const taskRouter = router({
// GET all tasks
list: publicProcedure
.input(
z
.object({
filter: z.enum(["all", "active", "completed"]).default("all"),
})
.optional()
)
.query(({ input }) => {
const filter = input?.filter ?? "all";
if (filter === "active") return tasks.filter((t) => !t.completed);
if (filter === "completed") return tasks.filter((t) => t.completed);
return tasks;
}),
// GET single task by ID
byId: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
const task = tasks.find((t) => t.id === input.id);
if (!task) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Task with ID ${input.id} not found`,
});
}
return task;
}),
// CREATE a new task
create: publicProcedure
.input(createTaskSchema)
.mutation(({ input }) => {
const newTask: Task = {
id: crypto.randomUUID(),
title: input.title,
description: input.description,
completed: false,
createdAt: new Date(),
updatedAt: new Date(),
};
tasks.push(newTask);
return newTask;
}),
// UPDATE an existing task
update: publicProcedure
.input(updateTaskSchema)
.mutation(({ input }) => {
const index = tasks.findIndex((t) => t.id === input.id);
if (index === -1) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Task with ID ${input.id} not found`,
});
}
const updated = {
...tasks[index],
...input,
updatedAt: new Date(),
};
tasks[index] = updated;
return updated;
}),
// DELETE a task
delete: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(({ input }) => {
const index = tasks.findIndex((t) => t.id === input.id);
if (index === -1) {
throw new TRPCError({
code: "NOT_FOUND",
message: `Task with ID ${input.id} not found`,
});
}
const deleted = tasks.splice(index, 1)[0];
return deleted;
}),
});لاحظ كيف يتم التحقق من كل مدخل باستخدام Zod. إذا أرسل العميل بيانات غير صالحة، يعيد tRPC تلقائياً خطأ 400 مع رسائل تحقق مفصلة — ويكتشف TypeScript عدم التطابق وقت التجميع.
الخطوة 5: إنشاء الموجه الرئيسي
اجمع كل موجهاتك في موجه جذر واحد. أنشئ src/trpc/routers/_app.ts:
import { router } from "../init";
import { taskRouter } from "./task";
export const appRouter = router({
task: taskRouter,
});
// Export the type — this is the magic that enables end-to-end type safety
export type AppRouter = typeof appRouter;نوع AppRouter هو المفتاح. تقوم بتصدير هذا النوع واستيراده في العميل — لا يعبر أي كود تشغيل الحدود، فقط الأنواع. يستنتج TypeScript كل مدخل ومخرج وخطأ من إجراءاتك.
الخطوة 6: إعداد معالج المسار
اربط tRPC بـ Next.js باستخدام محول fetch. أنشئ src/app/api/trpc/[trpc]/route.ts:
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { createTRPCContext } from "@/trpc/init";
import { appRouter } from "@/trpc/routers/_app";
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: createTRPCContext,
});
export { handler as GET, handler as POST };هذا ينشئ مسار شامل في /api/trpc/*. كل إجراء tRPC يصبح تلقائياً نقطة نهاية — task.list يتوافق مع /api/trpc/task.list.
الخطوة 7: إنشاء عميل tRPC
الآن قم بإعداد التكامل من جانب العميل. تحتاج ملفين.
أولاً، أنشئ خطافات tRPC React في src/trpc/client.ts:
import { createTRPCReact } from "@trpc/react-query";
import type { AppRouter } from "./routers/_app";
export const trpc = createTRPCReact<AppRouter>();ثم أنشئ المزود في src/trpc/provider.tsx:
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { useState } from "react";
import { trpc } from "./client";
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 function TRPCProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 1000,
retry: 1,
},
},
}));
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
}),
],
})
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</trpc.Provider>
);
}أضف المزود إلى layout الجذر في src/app/layout.tsx:
import { TRPCProvider } from "@/trpc/provider";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<TRPCProvider>{children}</TRPCProvider>
</body>
</html>
);
}الخطوة 8: بناء واجهة مدير المهام
الآن الجزء الممتع — استخدام tRPC في مكوناتك مع أمان أنواع كامل.
أنشئ src/app/page.tsx:
"use client";
import { useState } from "react";
import { trpc } from "@/trpc/client";
export default function TaskManager() {
const [filter, setFilter] = useState<"all" | "active" | "completed">("all");
const [newTitle, setNewTitle] = useState("");
const [newDescription, setNewDescription] = useState("");
// Queries — fully typed, no manual type annotations needed
const tasksQuery = trpc.task.list.useQuery({ filter });
// Mutations with automatic cache invalidation
const utils = trpc.useUtils();
const createTask = trpc.task.create.useMutation({
onSuccess: () => {
utils.task.list.invalidate();
setNewTitle("");
setNewDescription("");
},
});
const updateTask = trpc.task.update.useMutation({
onSuccess: () => utils.task.list.invalidate(),
});
const deleteTask = trpc.task.delete.useMutation({
onSuccess: () => utils.task.list.invalidate(),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!newTitle.trim()) return;
createTask.mutate({
title: newTitle,
description: newDescription,
});
};
return (
<main className="max-w-2xl mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">Task Manager</h1>
{/* Create Task Form */}
<form onSubmit={handleSubmit} className="mb-8 space-y-4">
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="Task title..."
className="w-full p-3 border rounded-lg"
/>
<textarea
value={newDescription}
onChange={(e) => setNewDescription(e.target.value)}
placeholder="Description (optional)"
className="w-full p-3 border rounded-lg"
rows={2}
/>
<button
type="submit"
disabled={createTask.isPending}
className="px-6 py-3 bg-blue-600 text-white rounded-lg
hover:bg-blue-700 disabled:opacity-50"
>
{createTask.isPending ? "Adding..." : "Add Task"}
</button>
</form>
{/* Filter Tabs */}
<div className="flex gap-2 mb-6">
{(["all", "active", "completed"] as const).map((f) => (
<button
key={f}
onClick={() => setFilter(f)}
className={`px-4 py-2 rounded-lg capitalize ${
filter === f
? "bg-blue-600 text-white"
: "bg-gray-100 hover:bg-gray-200"
}`}
>
{f}
</button>
))}
</div>
{/* Task List */}
{tasksQuery.isLoading && <p>Loading tasks...</p>}
{tasksQuery.error && (
<p className="text-red-600">Error: {tasksQuery.error.message}</p>
)}
<ul className="space-y-3">
{tasksQuery.data?.map((task) => (
<li
key={task.id}
className="flex items-center gap-4 p-4 border rounded-lg"
>
<input
type="checkbox"
checked={task.completed}
onChange={() =>
updateTask.mutate({
id: task.id,
completed: !task.completed,
})
}
className="w-5 h-5"
/>
<div className="flex-1">
<h3
className={`font-medium ${
task.completed ? "line-through text-gray-400" : ""
}`}
>
{task.title}
</h3>
{task.description && (
<p className="text-sm text-gray-500">{task.description}</p>
)}
</div>
<button
onClick={() => deleteTask.mutate({ id: task.id })}
className="text-red-500 hover:text-red-700"
>
Delete
</button>
</li>
))}
</ul>
{tasksQuery.data?.length === 0 && (
<p className="text-center text-gray-500 py-8">
No tasks found. Create one above.
</p>
)}
</main>
);
}لاحظ الإكمال التلقائي. عندما تكتب trpc.task.، يعرض محررك list، byId، create، update، delete. عندما تكتب createTask.mutate({، تحصل على إكمال تلقائي لـ title و description بأنواعهما الدقيقة. هذا هو سحر tRPC — صفر تعريفات أنواع يدوية على العميل.
الخطوة 9: استدعاءات من جانب الخادم في Server Components
من أفضل ميزات tRPC هي استدعاء الإجراءات مباشرة من Server Components بدون حمل HTTP زائد.
أنشئ src/trpc/server.ts:
import { createCallerFactory } from "./init";
import { appRouter } from "./routers/_app";
const createCaller = createCallerFactory(appRouter);
export const serverTRPC = createCaller({
userId: null, // populate from session in real apps
});الآن استخدمه في Server Component. أنشئ src/app/stats/page.tsx:
import { serverTRPC } from "@/trpc/server";
export default async function StatsPage() {
// Direct function call — no HTTP, no serialization overhead
const allTasks = await serverTRPC.task.list({ filter: "all" });
const completedTasks = await serverTRPC.task.list({ filter: "completed" });
const completionRate =
allTasks.length > 0
? Math.round((completedTasks.length / allTasks.length) * 100)
: 0;
return (
<main className="max-w-2xl mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">Task Statistics</h1>
<div className="grid grid-cols-3 gap-4">
<div className="p-6 bg-blue-50 rounded-lg text-center">
<p className="text-3xl font-bold text-blue-600">{allTasks.length}</p>
<p className="text-gray-600">Total Tasks</p>
</div>
<div className="p-6 bg-green-50 rounded-lg text-center">
<p className="text-3xl font-bold text-green-600">
{completedTasks.length}
</p>
<p className="text-gray-600">Completed</p>
</div>
<div className="p-6 bg-purple-50 rounded-lg text-center">
<p className="text-3xl font-bold text-purple-600">
{completionRate}%
</p>
<p className="text-gray-600">Completion Rate</p>
</div>
</div>
</main>
);
}استدعاء serverTRPC.task.list() يعمل مباشرة على الخادم — نفس العملية، نفس الذاكرة، بدون قفزة شبكية. لا يزال TypeScript يفرض العقد الكامل.
الخطوة 10: إضافة وسيط التسجيل
يتيح لك وسيط tRPC إضافة اهتمامات عابرة مثل التسجيل وتحديد المعدل والتحليلات.
حدّث src/trpc/init.ts لإضافة وسيط تسجيل:
// Add this after the existing code in init.ts
const logger = t.middleware(async ({ path, type, next }) => {
const start = Date.now();
const result = await next();
const duration = Date.now() - start;
if (result.ok) {
console.log(`[tRPC] ${type} ${path} — ${duration}ms OK`);
} else {
console.error(`[tRPC] ${type} ${path} — ${duration}ms ERROR`);
}
return result;
});
export const loggedProcedure = t.procedure.use(logger);يمكنك تكديس الوسائط. يمكن للإجراء استخدام كل من logger و enforceAuth:
export const loggedProtectedProcedure = t.procedure
.use(logger)
.use(enforceAuth);الخطوة 11: أفضل ممارسات معالجة الأخطاء
يوفر tRPC معالجة أخطاء منظمة مبنية مسبقاً. إليك كيفية استخدامها بفعالية.
رمي الأخطاء في الإجراءات
import { TRPCError } from "@trpc/server";
// In your procedure
throw new TRPCError({
code: "BAD_REQUEST",
message: "Title cannot be empty",
cause: originalError, // optional — for debugging
});رموز الأخطاء المتاحة
| الرمز | حالة HTTP | متى تستخدم |
|---|---|---|
BAD_REQUEST | 400 | مدخل غير صالح |
UNAUTHORIZED | 401 | غير مسجل الدخول |
FORBIDDEN | 403 | لا يوجد إذن |
NOT_FOUND | 404 | مورد مفقود |
CONFLICT | 409 | إدخال مكرر |
TOO_MANY_REQUESTS | 429 | تحديد المعدل |
INTERNAL_SERVER_ERROR | 500 | خطأ غير متوقع |
معالجة الأخطاء على العميل
const createTask = trpc.task.create.useMutation({
onError: (error) => {
// Zod validation errors
if (error.data?.zodError) {
const fieldErrors = error.data.zodError.fieldErrors;
console.log("Validation errors:", fieldErrors);
return;
}
// tRPC errors
console.log("Error code:", error.data?.code);
console.log("Message:", error.message);
},
});الخطوة 12: التحديثات التفاؤلية
لواجهة سريعة الاستجابة، يمكنك تحديث الذاكرة المؤقتة قبل أن يستجيب الخادم:
const updateTask = trpc.task.update.useMutation({
onMutate: async (newData) => {
// Cancel outgoing refetches
await utils.task.list.cancel();
// Snapshot current data
const previous = utils.task.list.getData({ filter: "all" });
// Optimistically update
utils.task.list.setData({ filter: "all" }, (old) =>
old?.map((task) =>
task.id === newData.id ? { ...task, ...newData } : task
)
);
return { previous };
},
onError: (_err, _newData, context) => {
// Rollback on error
if (context?.previous) {
utils.task.list.setData({ filter: "all" }, context.previous);
}
},
onSettled: () => {
utils.task.list.invalidate();
},
});اختبار التطبيق
شغّل خادم التطوير:
npm run devافتح http://localhost:3000 وتحقق من:
- تحميل قائمة المهام مع المهمة الأولية
- إمكانية إنشاء مهام جديدة من النموذج
- النقر على مربع الاختيار يبدّل حالة الإكمال
- زر الحذف يزيل المهام
- تبويبات الفلتر تعمل بشكل صحيح
- زيارة
/statsلرؤية الإحصائيات المعروضة من جانب الخادم
الاختبار باستخدام curl
يمكنك أيضاً اختبار API مباشرة:
# List all tasks
curl "http://localhost:3000/api/trpc/task.list?input=%7B%7D"
# Create a task
curl -X POST "http://localhost:3000/api/trpc/task.create" \
-H "Content-Type: application/json" \
-d '{"json":{"title":"Test from curl","description":"Works!"}}'هيكل المشروع
إليك هيكل المشروع النهائي:
src/
├── app/
│ ├── api/trpc/[trpc]/
│ │ └── route.ts # معالج مسار tRPC
│ ├── stats/
│ │ └── page.tsx # Server Component مع استدعاءات من جانب الخادم
│ ├── layout.tsx # Layout الجذر مع TRPCProvider
│ └── page.tsx # واجهة مدير المهام
└── trpc/
├── client.ts # خطافات React (createTRPCReact)
├── init.ts # تهيئة tRPC، السياق، الوسيط
├── provider.tsx # مزود جانب العميل
├── server.ts # مستدعي جانب الخادم
└── routers/
├── _app.ts # الموجه الجذر
└── task.ts # إجراءات المهام
استكشاف الأخطاء وإصلاحها
أخطاء "Cannot find module"
تأكد أن tsconfig.json يحتوي على أسماء المسارات المستعارة:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}تحذيرات "Hydration mismatch"
تأكد أن TRPCProvider مُعلّم بـ "use client" ويغلف فقط أجزاء تطبيقك التي تحتاج خطافات tRPC من جانب العميل.
بيانات قديمة بعد التعديلات
استدعِ دائماً utils.task.list.invalidate() في onSuccess أو onSettled لإعادة جلب البيانات بعد التعديل.
أخطاء الأنواع بعد تغيير الإجراءات
إذا غيّرت مدخل/مخرج إجراء، قد يخزن TypeScript أنواعاً قديمة مؤقتاً. أعد تشغيل خادم TypeScript (Cmd+Shift+P → "TypeScript: Restart TS Server" في VS Code).
الخطوات التالية
الآن بعد أن لديك إعداد tRPC يعمل، فكّر في هذه التحسينات:
- إضافة قاعدة بيانات حقيقية — استبدل التخزين في الذاكرة بـ Drizzle ORM و PostgreSQL
- إضافة المصادقة — ادمج AuthJS v5 واملأ
ctx.userId - إضافة تحديثات في الوقت الحقيقي — استخدم اشتراكات tRPC مع WebSockets
- إضافة الاختبارات — استخدم
createCallerFactoryلاختبار الإجراءات الوحدوية - النشر — احزمه بـ Docker أو انشره على Vercel
الخلاصة
يغيّر tRPC بشكل جذري طريقة بناء واجهات برمجة التطبيقات في تطبيقات TypeScript. بدلاً من الحفاظ على تعريفات أنواع منفصلة للعميل والخادم، تكتب إجراءاتك مرة واحدة وتدع TypeScript يستنتج كل شيء. النتيجة هي:
- أخطاء أقل — يتم اكتشاف عدم تطابق الأنواع وقت التجميع، وليس في الإنتاج
- تطوير أسرع — إكمال تلقائي لكل استدعاء API، بدون تعريفات أنواع يدوية
- كود أقل — بدون نمط REST المتكرر، بدون محللات GraphQL، بدون خطوة توليد أكواد
- تجربة مطور أفضل — أعد تسمية حقل على الخادم ويشير TypeScript فوراً لكل استخدام على العميل
مع Next.js App Router، تحصل على أفضل ما في العالمين: العرض من جانب الخادم مع استدعاءات خادم بدون حمل زائد، والتفاعلية من جانب العميل مع أمان أنواع كامل. هذه هي المنصة التي تجعل تطوير TypeScript الكامل سلساً حقاً.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء تطبيق متكامل باستخدام Drizzle ORM و Next.js 15: قاعدة بيانات آمنة الأنواع من الصفر إلى الإنتاج
تعلّم كيفية بناء تطبيق متكامل آمن الأنواع باستخدام Drizzle ORM مع Next.js 15. يغطي هذا الدليل العملي تصميم المخططات والترحيلات وServer Actions وعمليات CRUD والنشر مع PostgreSQL.

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.

بناء ونشر واجهة برمجية بدون خوادم باستخدام Cloudflare Workers و Hono و D1
تعلّم كيف تبني واجهة برمجية REST جاهزة للإنتاج باستخدام Cloudflare Workers وإطار Hono وقاعدة بيانات D1 — من إعداد المشروع حتى النشر العالمي.