Next.js: API Routes و Route Handlers

آخر تحديث: 2026-08-26

تشكل Route Handlers طبقة API في Next.js — عندما لا تكون Server Actions كافية، تبقى نقاط نهاية RESTful القياسية حجر الزاوية للويب الحديث.

1. ما ستتعلمه



2. قصة حقيقية لمهندس DevOps

(1) نقطة الألم: نفذ الفريق منطق المصادقة ثماني مرات عبر خمس صفحات.

أثناء مراجعة كود TaskFlow، لاحظت ديانا أن كل مسار API يبدأ بنفس 15 سطرًا من كود المصادقة — استخراج الجلسة من الكوكيز، التحقق من الرمز، وإرجاع استجابة 401. ثماني نقاط نهاية API × 15 سطرًا = 120 سطرًا من الكود المكرر. والأسوأ من ذلك، أن ثلاث نقاط نهاية كانت تفتقر إلى تحقق المصادقة، مما كشف بيانات المستخدم مباشرة للطلبات القادمة من مستخدمين غير مصدقين.

السؤال البيانات
كود المصادقة المكرر 120 سطرًا (8 نقاط نهاية × 15 سطرًا)
نقاط النهاية غير المحمية 3
فشل تدقيق الأمان مرتان
وقت الإصلاح 4 ساعات لكل جلسة

(2) حل Middleware

استخدم middleware.ts لإدارة المصادقة والتسجيل و CORS بشكل مركزي — ملف واحد يتحكم في دورة حياة جميع الطلبات بأكملها.

TS
// middleware.ts — تسجيل دخول موحد + سجل
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const token = request.cookies.get('session-token')?.value

  // فحص مصادقة طلبات API
  if (request.nextUrl.pathname.startsWith('/api/')) {
    if (!token) {
      return NextResponse.json({ error: 'غير مصرح' }, { status: 401 })
    }
  }

  // إضافة خوذة أمان
  const response = NextResponse.next()
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')

  return response
}

export const config = {
  matcher: '/api/:path*',
}

(3) المكاسب

البُعد قبل (بدون middleware) بعد (مع middleware)
كود التحقق 120 سطرًا (مبعثرًا) 15 سطرًا (مكثفًا)
نقاط النهاية غير المحمية 3 0
تدقيق الأمان ❌ فشل ✅ نجاح
جهد نقطة النهاية الجديدة 15 سطر مصادقة + المنطق المنطق فقط


3. أساسيات Route Handlers

تُعرّف Route Handlers في ملف app/api/**/route.ts، مع دالة غير متزامنة مُصدَّرة لكل طريقة HTTP:

طريقة HTTP الدالة المُصدَّرة ملف المسار
GET export async function GET() app/api/items/route.ts
POST export async function POST() app/api/items/route.ts
PUT export async function PUT() app/api/items/[id]/route.ts
DELETE export async function DELETE() app/api/items/[id]/route.ts
PATCH export async function PATCH() app/api/items/[id]/route.ts

(1) نقطة نهاية GET أساسية

TS
// app/api/items/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  const items = await db.item.findMany()
  return NextResponse.json(items)
}

(2) معاملات التوجيه الديناميكية

TS
// app/api/items/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const item = await db.item.findUnique({ where: { id: Number(params.id) } })
  if (!item) {
    return NextResponse.json({ error: 'العنصر غير موجود' }, { status: 404 })
  }
  return NextResponse.json(item)
}

(3) POST لإنشاء مورد

TS
// app/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const body = await request.json()
  const item = await db.item.create({ data: body })
  return NextResponse.json(item, { status: 201 })
}

▶ مثال: Route Handler كامل لـ CRUD (المستوى: ⭐⭐)

الناتج:

TEXT 📖 للعرض فقط
POST app/api/items/route.ts → ينشئ موردًا جديدًا، يُرجع العنصر المنشأ مع الحالة 201.
TS
// app/api/todos/route.ts — GET + POST
import { NextRequest, NextResponse } from 'next/server'

const API = 'https://jsonplaceholder.typicode.com'

export async function GET() {
  const todos = await fetch(`${API}/todos?_limit=5`).then(r => r.json())
  return NextResponse.json(todos)
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  const todo = await fetch(`${API}/todos`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then(r => r.json())
  return NextResponse.json(todo, { status: 201 })
}

الناتج:

TEXT 📖 للعرض فقط
GET app/api/todos/route.ts → يُرجع بيانات JSON مع الحالة 200.
POST app/api/todos/route.ts → ينشئ موردًا جديدًا، يُرجع العنصر المنشأ مع الحالة 201.
TS
// app/api/todos/[id]/route.ts — GET + PUT + DELETE
import { NextRequest, NextResponse } from 'next/server'

const API = 'https://jsonplaceholder.typicode.com'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const todo = await fetch(`${API}/todos/${params.id}`).then(r => r.json())
  if (!todo || todo.id === undefined) {
    return NextResponse.json({ error: 'غير موجود' }, { status: 404 })
  }
  return NextResponse.json(todo)
}

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const updated = await fetch(`${API}/todos/${params.id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  }).then(r => r.json())
  return NextResponse.json(updated)
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await fetch(`${API}/todos/${params.id}`, { method: 'DELETE' })
  return NextResponse.json({ success: true })
}


4. التحقق من صحة الطلبات عبر Zod

للتحقق من صحة بيانات الطلب في REST APIs، نوصي أيضًا باستخدام Zod — إنه أكثر إيجازًا وأمانًا من حيث الأنواع مقارنة بفحوصات if اليدوية.

(1) التحقق من صحة جسم الطلب

TS
// app/api/items/route.ts — التحقق من جسم طلب POST عبر Zod
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const createItemSchema = z.object({
  name: z.string().min(2).max(100),
  price: z.number().positive('يجب أن يكون السعر موجبًا'),
  category: z.enum(['electronics', 'clothing', 'food']),
  tags: z.array(z.string()).max(5).optional(),
})

export async function POST(request: NextRequest) {
  const body = await request.json()
  const validated = createItemSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'فشل التحقق', details: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const item = await db.item.create({ data: validated.data })
  return NextResponse.json(item, { status: 201 })
}

(2) التحقق من صحة معاملات الاستعلام

TS
// app/api/items/route.ts — التحقق من معاملات الاستعلام عبر Zod
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const querySchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().min(1).max(100).default(10),
  search: z.string().optional(),
})

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl
  const query = querySchema.safeParse({
    page: searchParams.get('page'),
    limit: searchParams.get('limit'),
    search: searchParams.get('search'),
  })

  if (!query.success) {
    return NextResponse.json(
      { error: 'معاملات استعلام غير صالحة', details: query.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const { page, limit, search } = query.data
  const items = await db.item.findMany({
    skip: (page - 1) * limit,
    take: limit,
    where: search ? { name: { contains: search } } : undefined,
  })
  return NextResponse.json({ items, page, limit })
}

▶ مثال: API كامل مع التحقق عبر Zod (المستوى: ⭐⭐)

الناتج:

TEXT 📖 للعرض فقط
GET app/api/items/route.ts → يتحقق من معاملات الاستعلام عبر Zod، يُرجع النتائج المصفاة كـ JSON.
TS
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { revalidateTag } from 'next/cache'

const userSchema = z.object({
  name: z.string().min(2, 'الاسم قصير جدًا'),
  email: z.string().email('بريد إلكتروني غير صالح'),
  role: z.enum(['admin', 'user', 'viewer']).default('user'),
})

export async function POST(request: NextRequest) {
  const body = await request.json()
  const validated = userSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'فشل التحقق', fields: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const user = await fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(validated.data),
  }).then(r => r.json())

  revalidateTag('users')
  return NextResponse.json(user, { status: 201 })
}

الناتج:

TEXT 📖 للعرض فقط
POST app/api/users/route.ts → يتحقق من جسم الطلب عبر Zod، ينشئ المورد، يُرجع 201.


5. تنسيق استجابة NextResponse

NextResponse هي أداة استجابة مخصصة تقدمها Next.js تدعم تنسيقات استجابة متعددة:

الطريقة الغرض مثال
NextResponse.json(data, opts?) استجابة JSON NextResponse.json({ id: 1 }, { status: 201 })
NextResponse.redirect(url) إعادة توجيه NextResponse.redirect(new URL('/login', request.url))
NextResponse.next() متابعة سلسلة middleware return NextResponse.next()
NextResponse.rewrite(url) إعادة كتابة URL من جهة الخادم NextResponse.rewrite(new URL('/fallback', request.url))

(1) استجابة JSON

TS
// app/api/auth/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  return NextResponse.json({ status: 'healthy', uptime: process.uptime() })
}

(2) إعادة التوجيه

TS
// app/api/redirect/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const target = request.nextUrl.searchParams.get('to') ?? '/'
  return NextResponse.redirect(new URL(target, request.url))
}

(3) Rewrite (إعادة كتابة URL)

TS
// middleware.ts — إعادة كتابة /products/old-slug إلى مسار جديد
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/products/old-')) {
    const newPath = request.nextUrl.pathname.replace('/old-', '/')
    return NextResponse.rewrite(new URL(newPath, request.url))
  }
  return NextResponse.next()
}

▶ مثال: مقارنة تنسيقات الاستجابة (المستوى: ⭐)

الناتج:

TEXT 📖 للعرض فقط
يعترض Middleware الطلبات ويعيد كتابة عناوين URL بناءً على الشروط.
TS
// app/api/response-demo/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const format = request.nextUrl.searchParams.get('format') ?? 'json'

  switch (format) {
    case 'json':
      return NextResponse.json({ message: 'مرحبًا', timestamp: Date.now() })
    case 'redirect':
      return NextResponse.redirect(new URL('/api/response-demo?format=json', request.url))
    case 'rewrite':
      return NextResponse.rewrite(new URL('/api/response-demo?format=json', request.url))
    default:
      return NextResponse.json({ error: 'تنسيق غير معروف' }, { status: 400 })
  }
}

الناتج:

TEXT 📖 للعرض فقط
GET app/api/response-demo/route.ts → يُرجع بيانات JSON مع الحالة 200.


6. Middleware

middleware.ts هو معترض طلبات لـ Next.js — يعمل قبل وصول كل طلب إلى صفحة أو API، ويدعم مطابقة المسارات وإعادة كتابة الطلبات وحقن الرؤوس وفحوصات المصادقة.

100%
sequenceDiagram
    participant Client as المتصفح
    participant MW as middleware.ts
    participant Route as Route Handler/صفحة

    Client->>MW: طلب /api/todos
    MW->>MW: مطابقة قواعد matcher
    MW->>MW: فحص المصادقة / حقن الرؤوس
    alt مصرح
        MW->>Route: NextResponse.next()
        Route-->>Client: استجابة عادية
    else غير مصرح
        MW-->>Client: NextResponse.json(401)
    else إعادة توجيه
        MW-->>Client: NextResponse.redirect(/login)
    end
الإعداد النوع الوصف
matcher string[] مطابقة نمط المسار (تدعم glob)
request.nextUrl URL كائن URL للطلب الحالي
request.cookies Map كوكيز الطلب
request.headers Headers رؤوس الطلب
NextResponse.next() Response متابعة المعالجة بشكل طبيعي
NextResponse.redirect() Response إعادة التوجيه إلى URL آخر

(1) تكوين matcher

TS
// middleware.ts — تخطيط matcher
export const config = {
  matcher: [
    '/api/:path*',           // جميع مسارات API
    '/dashboard/:path*',     // جميع صفحات لوحة التحكم
    '/((?!_next|static|favicon.ico).*)',  // استبعاد الموارد الثابتة
  ],
}

(2) أنماط Middleware الشائعة

TS
// middleware.ts — ملخص الأنماط الشائعة
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // 1. فحص المصادقة
  const token = request.cookies.get('session')?.value
  const isAuthPage = pathname.startsWith('/login') || pathname.startsWith('/register')

  if (!token && !isAuthPage && pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // 2. رؤوس الأمان
  const response = NextResponse.next()
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')

  // 3. إضافة معرف الطلب (تتبع)
  const requestId = crypto.randomUUID()
  response.headers.set('X-Request-Id', requestId)

  return response
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}

▶ مثال: تسجيل Middleware (المستوى: ⭐)

الناتج:

TEXT 📖 للعرض فقط
يعترض Middleware الطلبات ويعيد التوجيه بناءً على الشروط.
TS
// middleware.ts — سجلات الطلبات
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const start = Date.now()
  const { method, nextUrl } = request

  console.log(`[${new Date().toISOString()}] ${method} ${nextUrl.pathname}`)

  const response = NextResponse.next()

  // تسجيل زمن الاستجابة بشكل غير متزامن
  response.headers.set('X-Response-Time', `${Date.now() - start}ms`)

  return response
}

export const config = {
  matcher: '/api/:path*',
}

الناتج:

TEXT 📖 للعرض فقط
[${new Date(


7. CORS وتحديد المعدل

(1) تكوين CORS

TS
// app/api/cors-config/route.ts — لنقطة نهاية واحدة CORS
import { NextRequest, NextResponse } from 'next/server'

const corsHeaders = {
  'Access-Control-Allow-Origin': 'https://your-app.com',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  'Access-Control-Max-Age': '86400',
}

export async function OPTIONS() {
  return NextResponse.json({}, { headers: corsHeaders })
}

export async function GET() {
  return NextResponse.json(
    { data: 'CORS مفعل' },
    { headers: corsHeaders }
  )
}

(2) Middleware عام لـ CORS

TS
// middleware.ts — CORS عام
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const response = NextResponse.next()
    response.headers.set('Access-Control-Allow-Origin', '*')
    response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
    response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')

    // طلبات الفحص المسبق تُعاد فورًا
    if (request.method === 'OPTIONS') {
      return new Response(null, { status: 204, headers: response.headers })
    }

    return response
  }
  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

(3) تحديد المعدل

TS
// lib/rate-limit.ts — محدد معدل بسيط في الذاكرة
const rateMap = new Map<string, { count: number; resetAt: number }>()

export function rateLimit(key: string, maxRequests: number, windowMs: number): boolean {
  const now = Date.now()
  const record = rateMap.get(key)

  if (!record || now > record.resetAt) {
    rateMap.set(key, { count: 1, resetAt: now + windowMs })
    return true  // مسموح
  }

  if (record.count >= maxRequests) {
    return false  // ممنوع
  }

  record.count++
  return true
}
TS
// app/api/rate-limited/route.ts — استخدام محدد المعدل
import { NextRequest, NextResponse } from 'next/server'
import { rateLimit } from '@/lib/rate-limit'

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
  const allowed = rateLimit(ip, 10, 60_000)  // 10 طلبات في الدقيقة

  if (!allowed) {
    return NextResponse.json(
      { error: 'طلبات كثيرة جدًا' },
      { status: 429, headers: { 'Retry-After': '60' } }
    )
  }

  return NextResponse.json({ message: 'نجاح', timestamp: Date.now() })
}

▶ مثال: CORS كامل + تحديد المعدل (المستوى: ⭐⭐⭐)

الناتج:

TEXT 📖 للعرض فقط
GET app/api/rate-limited/route.ts → يُرجع البيانات كـ JSON. محدد المعدل لمنع الإساءة.
TS
// app/api/secure/route.ts — CORS + تحديد المعدل + مصادقة
import { NextRequest, NextResponse } from 'next/server'
import { rateLimit } from '@/lib/rate-limit'
import { z } from 'zod'

const postSchema = z.object({
  title: z.string().min(2).max(200),
  content: z.string().min(10),
})

export async function POST(request: NextRequest) {
  // 1. تحديد المعدل
  const ip = request.headers.get('x-forwarded-for') ?? 'unknown'
  if (!rateLimit(ip, 20, 60_000)) {
    return NextResponse.json({ error: 'تم تجاوز حد المعدل' }, { status: 429 })
  }

  // 2. فحص المصادقة
  const auth = request.headers.get('authorization')
  if (!auth?.startsWith('Bearer ') || auth.slice(7) !== process.env.API_KEY) {
    return NextResponse.json({ error: 'غير مصرح' }, { status: 401 })
  }

  // 3. التحقق
  const body = await request.json()
  const validated = postSchema.safeParse(body)
  if (!validated.success) {
    return NextResponse.json(
      { error: 'فشل التحقق', details: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  // 4. المعالجة
  const post = await fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'POST',
    body: JSON.stringify({ ...validated.data, userId: 1 }),
  }).then(r => r.json())

  return NextResponse.json(post, { status: 201, headers: {
    'Access-Control-Allow-Origin': '*',
  }})
}

export async function OPTIONS() {
  return NextResponse.json({}, { headers: {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  }})
}

الناتج:

TEXT 📖 للعرض فقط
POST app/api/secure/route.ts → يتحقق من جسم الطلب عبر Zod، ينشئ المورد، يُرجع 201.


8. مثال كامل: خدمة RESTful API

TS
// app/api/posts/route.ts — API CRUD للمقالات
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { revalidateTag } from 'next/cache'

// ======== المخططات ========
const createPostSchema = z.object({
  title: z.string().min(2).max(200),
  body: z.string().min(10).max(5000),
  userId: z.number().int().positive(),
})

const querySchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().min(1).max(50).default(10),
})

const API = 'https://jsonplaceholder.typicode.com'

// ======== GET /api/posts?page=1&limit=10 ========
export async function GET(request: NextRequest) {
  const query = querySchema.safeParse({
    page: request.nextUrl.searchParams.get('page'),
    limit: request.nextUrl.searchParams.get('limit'),
  })

  if (!query.success) {
    return NextResponse.json(
      { error: 'استعلام غير صالح', details: query.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const { page, limit } = query.data
  const posts = await fetch(`${API}/posts?_page=${page}&_limit=${limit}`).then(r => r.json())
  const total = 100  // إجمالي JSONPlaceholder

  return NextResponse.json({
    data: posts,
    pagination: { page, limit, total, totalPages: Math.ceil(total / limit) }
  })
}

// ======== POST /api/posts ========
export async function POST(request: NextRequest) {
  const body = await request.json()
  const validated = createPostSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'فشل التحقق', fields: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const post = await fetch(`${API}/posts`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(validated.data),
  }).then(r => r.json())

  revalidateTag('posts')
  return NextResponse.json(post, { status: 201 })
}
TS
// app/api/posts/[id]/route.ts — CRUD لمقال واحد
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const updatePostSchema = z.object({
  title: z.string().min(2).max(200).optional(),
  body: z.string().min(10).max(5000).optional(),
})

const API = 'https://jsonplaceholder.typicode.com'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const post = await fetch(`${API}/posts/${params.id}`).then(r => {
    if (!r.ok) throw new Error('غير موجود')
    return r.json()
  }).catch(() => null)

  if (!post) {
    return NextResponse.json({ error: 'المقال غير موجود' }, { status: 404 })
  }
  return NextResponse.json(post)
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await request.json()
  const validated = updatePostSchema.safeParse(body)

  if (!validated.success) {
    return NextResponse.json(
      { error: 'فشل التحقق', fields: validated.error.flatten().fieldErrors },
      { status: 400 }
    )
  }

  const updated = await fetch(`${API}/posts/${params.id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(validated.data),
  }).then(r => r.json())

  return NextResponse.json(updated)
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  await fetch(`${API}/posts/${params.id}`, { method: 'DELETE' })
  return NextResponse.json({ success: true, id: params.id })
}
TS
// middleware.ts — Middleware API عام
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { rateLimit } from '@/lib/rate-limit'

export function middleware(request: NextRequest) {
  const { pathname, origin } = request.nextUrl

  if (!pathname.startsWith('/api/')) {
    return NextResponse.next()
  }

  // 1. تحديد المعدل
  const ip = request.headers.get('x-forwarded-for') ?? 'unknown'
  if (!rateLimit(ip, 30, 60_000)) {
    return NextResponse.json({ error: 'طلبات كثيرة جدًا' }, {
      status: 429,
      headers: { 'Retry-After': '60' }
    })
  }

  // 2. التسجيل
  console.log(`[API] ${request.method} ${pathname}`)

  // 3. CORS
  const response = NextResponse.next()
  response.headers.set('Access-Control-Allow-Origin', '*')
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key')

  if (request.method === 'OPTIONS') {
    return new Response(null, { status: 204, headers: response.headers })
  }

  return response
}

export const config = {
  matcher: '/api/:path*',
}

❓ أسئلة شائعة

س ما الفرق بين Route Handler و Server Action؟
ج Route Handler هو نقطة نهاية HTTP قياسية تُعرّف باستخدام route.ts، مناسبة لتكامل API الطرف الثالث و webhooks واستدعاءات تطبيقات الجوال. Server Action هي دالة من جهة الخادم بنمط RPC تُعرّف باستخدام 'use server'، مناسبة لعمليات نماذج الطرف الأول. الفروقات الرئيسية: Route Handlers تتطلب استدعاءات HTTP، بينما Server Actions هي استدعاءات دوال؛ Route Handlers لا تتضمن حماية CSRF، بينما Server Actions تحتوي عليها بشكل مدمج.
س ما الفرق بين NextResponse.json و new Response() البسيط؟
ج NextResponse.json هي طريقة ملائمة من NextResponse تقوم تلقائيًا بتعيين Content-Type: application/json وتوفر استدلالًا أفضل لأنواع TypeScript. new Response(JSON.stringify(data), { headers: {'Content-Type': 'application/json'} }) مكافئ. نوصي باستخدام NextResponse.json للحفاظ على كود موجز.
س هل يمكن لـ middleware.ts قراءة قاعدة البيانات؟
ج نعم، ولكن انتبه للأداء — middleware يعمل لكل طلب مطابق. استعلامات قاعدة البيانات تزيد من زمن الاستجابة. يُوصى بإجراء عمليات خفيفة فقط في middleware (مثل تحليل الكوكيز وفحص الرؤوس وإعادة التوجيه). يجب وضع عمليات قاعدة البيانات في route handlers أو server actions.
س هل يدعم Route Handler Edge Runtime؟
ج نعم، يدعمه. افتراضيًا، يعمل Route Handler على Node.js Runtime، ولكن يمكنك التبديل إلى Edge Runtime عبر export const runtime = 'edge'. يوفر Edge Runtime زمن استجابة أقل ولكن بدعم محدود لـ API (لا يدعم وحدات Node.js الأصلية مثل fs و crypto). Edge Runtime مناسب للوكالة البسيطة والتحقق من المصادقة واختبارات A/B.
س كيف يمكنني حماية نقاط نهاية API من الإساءة؟
ج ثلاث طبقات من الحماية: ① تحديد المعدل بناءً على IP في طبقة middleware؛ ② مصادقة API key/JWT في طبقة route handler؛ ③ قيود CORS عامة على النطاقات المسموح بها. في بيئات الإنتاج، نوصي باستخدام محدد معدل مدعوم بـ Redis (مثل upstash/ratelimit) لدعم مشاركة الحالة عبر مثيلات متعددة.
س كيف أسترد عنوان IP الخاص بالعميل في Route Handler؟
ج استخدم request.headers لاسترداده: request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() أو request.headers.get('x-real-ip'). Vercel تضيف هذه الرؤوس تلقائيًا أثناء النشر. عند التطوير محليًا، قد تُرجع ::1 أو 127.0.0.1.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): أنشئ app/api/health/route.ts لإرجاع معلومات فحص الصحة بتنسيق JSON (الحالة، الطابع الزمني، وقت التشغيل). أضف middleware لتسجيل كل استدعاء API وزمن استجابته.

  2. تمرين متقدم (⭐⭐): أنشئ API كامل لـ CRUD: app/api/books/route.ts (GET للقائمة + POST للإنشاء) + app/api/books/[id]/route.ts (GET لعرض التفاصيل + PUT للتحديث + DELETE للحذف). استخدم Zod للتحقق من صحة جسم الطلب. أضف مصادقة API key في middleware.

  3. تحدي (⭐⭐⭐): نفذ خدمة "اختصار الروابط" API: app/api/shorten/route.ts (POST يستقبل URL ويُعيد رابطًا مختصرًا)، app/api/[code]/route.ts (GET يسترد رابطًا مختصرًا ويُجري إعادة توجيه 302). أضف تحديد المعدل (10 روابط مختصرة في الدقيقة لكل IP)، ودعم CORS، وسجلات وصول middleware. خزّن الرموز المختصرة في ملف JSON أو خريطة في الذاكرة.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%