مقدمة إلى مسارات API ومعالجات المسارات
يتيح لك Next.js بناء تطبيقات Full-stack من خلال توفير نظام توجيه API قوي. مع معالجات المسارات، يمكنك إنشاء نقاط نهاية RESTful API مباشرة داخل تطبيق Next.js الخاص بك دون الحاجة إلى خادم خلفي منفصل. يغطي هذا الدرس نهج App Router باستخدام ملفات route.ts ومعالجة الطلبات/الاستجابات الحديثة.
ملاحظة: يستخدم App Router (Next.js 13+) معالجات المسارات في ملفات route.ts/route.js، مما يحل محل نهج دليل pages/api القديم من Pages Router. توفر معالجات المسارات دعمًا أفضل لـ TypeScript وتعمل بسلاسة مع React Server Components.
إنشاء معالجات المسارات
يتم تعريف معالجات المسارات في ملفات route.ts (أو route.js) خاصة داخل دليل app. كل معالج مسار يصدّر دوال مسماة بأسماء طرق HTTP: GET، POST، PUT، DELETE، PATCH، إلخ.
هيكل معالج المسار الأساسي
// app/api/hello/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
return NextResponse.json({ message: 'مرحباً من Next.js API!' });
}
export async function POST(request: NextRequest) {
const body = await request.json();
return NextResponse.json({ received: body }, { status: 201 });
}
هذا ينشئ نقطة نهاية API على /api/hello تستجيب لكل من طلبات GET و POST. يتطابق عنوان URL للنقطة النهائية مع مسار الملف داخل دليل app.
دعم طرق HTTP
تدعم معالجات المسارات في Next.js جميع طرق HTTP القياسية:
// app/api/products/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const productId = params.id;
// جلب المنتج من قاعدة البيانات
return NextResponse.json({ id: productId, name: 'منتج' });
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const productId = params.id;
const updates = await request.json();
// تحديث المنتج في قاعدة البيانات
return NextResponse.json({ id: productId, ...updates });
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const productId = params.id;
// حذف المنتج من قاعدة البيانات
return NextResponse.json({ deleted: true, id: productId });
}
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const productId = params.id;
const partialUpdate = await request.json();
// تحديث جزئي في قاعدة البيانات
return NextResponse.json({ id: productId, ...partialUpdate });
}
العمل مع كائنات الطلب
يوسع كائن NextRequest واجهة Web Request API القياسية بميزات خاصة بـ Next.js:
قراءة بيانات الطلب
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
// الحصول على جسم JSON
const body = await request.json();
// الحصول على معاملات بحث URL
const searchParams = request.nextUrl.searchParams;
const userId = searchParams.get('userId');
// الحصول على الرؤوس
const authorization = request.headers.get('authorization');
// الحصول على ملفات تعريف الارتباط
const token = request.cookies.get('auth-token');
return NextResponse.json({
body,
userId,
hasAuth: !!authorization,
hasToken: !!token
});
}
قراءة جسم الطلب (تنسيقات مختلفة)
export async function POST(request: NextRequest) {
// JSON
const jsonData = await request.json();
// بيانات النموذج
const formData = await request.formData();
const name = formData.get('name');
const file = formData.get('file') as File;
// نص عادي
const textData = await request.text();
// بيانات ثنائية
const arrayBuffer = await request.arrayBuffer();
const blob = await request.blob();
return NextResponse.json({ received: true });
}
تحذير: يمكنك قراءة جسم الطلب مرة واحدة فقط. بعد استدعاء request.json() أو request.text() أو أي طريقة أخرى لقراءة الجسم، يتم استهلاك تدفق الجسم ولا يمكن قراءته مرة أخرى. قم بتخزين البيانات في متغير إذا كنت بحاجة لاستخدامها عدة مرات.
العمل مع كائنات الاستجابة
يوفر NextResponse واجهة برمجة تطبيقات قوية لإنشاء وتخصيص الاستجابات:
استجابات JSON
// استجابة JSON أساسية
export async function GET() {
return NextResponse.json({ message: 'نجح' });
}
// مع رمز حالة مخصص
export async function POST() {
return NextResponse.json(
{ error: 'طلب خاطئ' },
{ status: 400 }
);
}
// مع رؤوس مخصصة
export async function GET() {
return NextResponse.json(
{ data: [] },
{
status: 200,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'قيمة مخصصة',
'Cache-Control': 'public, max-age=3600'
}
}
);
}
تعيين ملفات تعريف الارتباط
export async function POST(request: NextRequest) {
const response = NextResponse.json({ success: true });
// تعيين ملف تعريف ارتباط
response.cookies.set('auth-token', 'abc123', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7 // 7 أيام
});
// حذف ملف تعريف ارتباط
response.cookies.delete('old-token');
return response;
}
إعادة التوجيه
import { redirect } from 'next/navigation';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const authenticated = searchParams.get('auth') === 'true';
if (!authenticated) {
redirect('/login');
}
return NextResponse.json({ user: 'data' });
}
// أو باستخدام NextResponse.redirect
export async function POST(request: NextRequest) {
// معالجة النموذج...
return NextResponse.redirect(new URL('/success', request.url));
}
معاملات المسار الديناميكية
الوصول إلى المقاطع الديناميكية من URL باستخدام وسيطة params:
// app/api/posts/[postId]/comments/[commentId]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { postId: string; commentId: string } }
) {
const { postId, commentId } = params;
return NextResponse.json({
post: postId,
comment: commentId,
data: 'بيانات التعليق'
});
}
الاستجابات المتدفقة
يدعم Next.js الاستجابات المتدفقة للتعامل مع مجموعات البيانات الكبيرة أو البيانات في الوقت الفعلي:
استخدام ReadableStream
// app/api/stream/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// إرسال البيانات على دفعات
for (let i = 0; i < 10; i++) {
const data = encoder.encode(`قطعة ${i}\n`);
controller.enqueue(data);
// محاكاة التأخير
await new Promise(resolve => setTimeout(resolve, 1000));
}
controller.close();
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/plain',
'Transfer-Encoding': 'chunked'
}
});
}
الأحداث المرسلة من الخادم (SSE)
// app/api/events/route.ts
export async function GET(request: NextRequest) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
// إرسال الأحداث
const sendEvent = (data: any) => {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(data)}\n\n`)
);
};
// إرسال حدث أولي
sendEvent({ type: 'connected', timestamp: Date.now() });
// إرسال تحديثات دورية
const interval = setInterval(() => {
sendEvent({ type: 'update', value: Math.random() });
}, 2000);
// التنظيف عند قطع الاتصال
request.signal.addEventListener('abort', () => {
clearInterval(interval);
controller.close();
});
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
}
نصيحة: الأحداث المرسلة من الخادم (SSE) مثالية للتحديثات في الوقت الفعلي مثل الإشعارات والنتائج المباشرة وأسعار الأسهم. على عكس WebSockets، تستخدم SSE HTTP القياسي وتعيد الاتصال تلقائيًا إذا انقطع الاتصال.
معالجة الأخطاء
قم بتطبيق معالجة الأخطاء المناسبة في معالجات المسارات الخاصة بك:
// app/api/users/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const userId = parseInt(params.id);
if (isNaN(userId)) {
return NextResponse.json(
{ error: 'معرف المستخدم غير صالح' },
{ status: 400 }
);
}
// جلب المستخدم من قاعدة البيانات
const user = await fetchUser(userId);
if (!user) {
return NextResponse.json(
{ error: 'المستخدم غير موجود' },
{ status: 404 }
);
}
return NextResponse.json({ user });
} catch (error) {
console.error('خطأ في جلب المستخدم:', error);
return NextResponse.json(
{ error: 'خطأ داخلي في الخادم' },
{ status: 500 }
);
}
}
تكوين CORS
قم بتكوين مشاركة الموارد عبر المصادر (CORS) للوصول إلى API الخارجي:
// app/api/public/route.ts
export async function GET(request: NextRequest) {
const response = NextResponse.json({ data: 'عام' });
// السماح لجميع المصادر (استخدم بحذر)
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return response;
}
// معالجة طلب OPTIONS المسبق
export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
});
}
التحقق من صحة الطلب
تحقق من صحة الطلبات الواردة باستخدام مكتبات مثل Zod:
// app/api/users/route.ts
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().min(18).max(120)
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// التحقق من صحة جسم الطلب
const validatedData = userSchema.parse(body);
// معالجة البيانات المعتمدة
const user = await createUser(validatedData);
return NextResponse.json({ user }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'فشل التحقق', issues: error.issues },
{ status: 422 }
);
}
return NextResponse.json(
{ error: 'خطأ داخلي في الخادم' },
{ status: 500 }
);
}
}
تكوين معالج المسار
قم بتكوين سلوك معالج المسار باستخدام تصدير config الخاص:
// app/api/data/route.ts
// الاختيار في العرض الديناميكي
export const dynamic = 'force-dynamic'; // 'auto' | 'force-dynamic' | 'error' | 'force-static'
// تعيين وقت التشغيل
export const runtime = 'edge'; // 'nodejs' | 'edge'
// تكوين إعادة التحقق
export const revalidate = 60; // إعادة التحقق كل 60 ثانية
export async function GET() {
return NextResponse.json({ timestamp: Date.now() });
}
تمرين: أنشئ REST API كاملة لنظام مدونة مع نقاط النهاية التالية:
- GET /api/posts - قائمة جميع المشاركات مع التصفح
- GET /api/posts/[id] - الحصول على مشاركة واحدة
- POST /api/posts - إنشاء مشاركة جديدة (مع التحقق)
- PUT /api/posts/[id] - تحديث مشاركة
- DELETE /api/posts/[id] - حذف مشاركة
- GET /api/posts/[id]/comments - الحصول على تعليقات لمشاركة
قم بتضمين معالجة الأخطاء المناسبة والتحقق من صحة الطلب وأكواد حالة HTTP المناسبة.
الملخص
توفر معالجات المسارات في Next.js طريقة قوية ومرنة لبناء نقاط نهاية API مباشرة داخل تطبيقك. النقاط الرئيسية:
- استخدم ملفات route.ts في دليل app لتحديد نقاط نهاية API
- صدّر دوال مسماة بأسماء طرق HTTP (GET، POST، PUT، DELETE، إلخ)
- توفر NextRequest و NextResponse واجهات برمجة تطبيقات غنية لمعالجة الطلبات والاستجابات
- دعم الاستجابات المتدفقة والأحداث المرسلة من الخادم للبيانات في الوقت الفعلي
- معاملات المسار الديناميكية تعمل تمامًا مثل مسارات الصفحة
- قم بتطبيق معالجة الأخطاء المناسبة والتحقق من صحة الطلب
- قم بتكوين CORS عند الحاجة للوصول إلى API الخارجي
- استخدم صادرات التكوين للتحكم في سلوك التخزين المؤقت ووقت التشغيل