Next.js: API Routes و Route Handlers
آخر تحديث: 2026-08-26
تشكل Route Handlers طبقة API في Next.js — عندما لا تكون Server Actions كافية، تبقى نقاط نهاية RESTful القياسية حجر الزاوية للويب الحديث.
1. ما ستتعلمه
- ملف
route.tsيعرّف نقاط نهاية GET/POST/PUT/DELETE - Zod يتحقق من صحة جسم الطلب ومعاملات الاستعلام
- تنسيق استجابة
NextResponse(json/redirect/rewrite) middleware.tsتكوين المطابقات وإعادة كتابة الطلبات- تكوين CORS عبر النطاقات وتنفيذ تحديد المعدل
2. قصة حقيقية لمهندس DevOps
(1) نقطة الألم: نفذ الفريق منطق المصادقة ثماني مرات عبر خمس صفحات.
أثناء مراجعة كود TaskFlow، لاحظت ديانا أن كل مسار API يبدأ بنفس 15 سطرًا من كود المصادقة — استخراج الجلسة من الكوكيز، التحقق من الرمز، وإرجاع استجابة 401. ثماني نقاط نهاية API × 15 سطرًا = 120 سطرًا من الكود المكرر. والأسوأ من ذلك، أن ثلاث نقاط نهاية كانت تفتقر إلى تحقق المصادقة، مما كشف بيانات المستخدم مباشرة للطلبات القادمة من مستخدمين غير مصدقين.
| السؤال | البيانات |
|---|---|
| كود المصادقة المكرر | 120 سطرًا (8 نقاط نهاية × 15 سطرًا) |
| نقاط النهاية غير المحمية | 3 |
| فشل تدقيق الأمان | مرتان |
| وقت الإصلاح | 4 ساعات لكل جلسة |
(2) حل Middleware
استخدم
middleware.tsلإدارة المصادقة والتسجيل و CORS بشكل مركزي — ملف واحد يتحكم في دورة حياة جميع الطلبات بأكملها.
// 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 أساسية
// 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) معاملات التوجيه الديناميكية
// 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 لإنشاء مورد
// 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 (المستوى: ⭐⭐)
الناتج:
POST app/api/items/route.ts → ينشئ موردًا جديدًا، يُرجع العنصر المنشأ مع الحالة 201.
// 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 })
}
الناتج:
GET app/api/todos/route.ts → يُرجع بيانات JSON مع الحالة 200.
POST app/api/todos/route.ts → ينشئ موردًا جديدًا، يُرجع العنصر المنشأ مع الحالة 201.
// 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) التحقق من صحة جسم الطلب
// 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) التحقق من صحة معاملات الاستعلام
// 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 (المستوى: ⭐⭐)
الناتج:
GET app/api/items/route.ts → يتحقق من معاملات الاستعلام عبر Zod، يُرجع النتائج المصفاة كـ JSON.
// 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 })
}
الناتج:
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
// app/api/auth/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({ status: 'healthy', uptime: process.uptime() })
}
(2) إعادة التوجيه
// 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)
// 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()
}
▶ مثال: مقارنة تنسيقات الاستجابة (المستوى: ⭐)
الناتج:
يعترض Middleware الطلبات ويعيد كتابة عناوين URL بناءً على الشروط.
// 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 })
}
}
الناتج:
GET app/api/response-demo/route.ts → يُرجع بيانات JSON مع الحالة 200.
6. Middleware
middleware.ts هو معترض طلبات لـ Next.js — يعمل قبل وصول كل طلب إلى صفحة أو API، ويدعم مطابقة المسارات وإعادة كتابة الطلبات وحقن الرؤوس وفحوصات المصادقة.
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
// middleware.ts — تخطيط matcher
export const config = {
matcher: [
'/api/:path*', // جميع مسارات API
'/dashboard/:path*', // جميع صفحات لوحة التحكم
'/((?!_next|static|favicon.ico).*)', // استبعاد الموارد الثابتة
],
}
(2) أنماط Middleware الشائعة
// 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 (المستوى: ⭐)
الناتج:
يعترض Middleware الطلبات ويعيد التوجيه بناءً على الشروط.
// 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*',
}
الناتج:
[${new Date(
7. CORS وتحديد المعدل
(1) تكوين CORS
// 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
// 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) تحديد المعدل
// 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
}
// 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 كامل + تحديد المعدل (المستوى: ⭐⭐⭐)
الناتج:
GET app/api/rate-limited/route.ts → يُرجع البيانات كـ JSON. محدد المعدل لمنع الإساءة.
// 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',
}})
}
الناتج:
POST app/api/secure/route.ts → يتحقق من جسم الطلب عبر Zod، ينشئ المورد، يُرجع 201.
8. مثال كامل: خدمة RESTful API
// 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 })
}
// 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 })
}
// 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.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 قراءة قاعدة البيانات؟export const runtime = 'edge'. يوفر Edge Runtime زمن استجابة أقل ولكن بدعم محدود لـ API (لا يدعم وحدات Node.js الأصلية مثل fs و crypto). Edge Runtime مناسب للوكالة البسيطة والتحقق من المصادقة واختبارات A/B.upstash/ratelimit) لدعم مشاركة الحالة عبر مثيلات متعددة.request.headers لاسترداده: request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() أو request.headers.get('x-real-ip'). Vercel تضيف هذه الرؤوس تلقائيًا أثناء النشر. عند التطوير محليًا، قد تُرجع ::1 أو 127.0.0.1.📖 ملخص
- ملف
route.tsيعرّف نقاط نهاية RESTful باستخدام دوال مثل GET و POST و PUT و DELETE - Zod يتحقق من صحة جسم الطلب ومعاملات الاستعلام، ثم يُرجع كود الحالة 400 وتفاصيل الخطأ
NextResponseتدعم أربعة تنسيقات استجابة: json() و redirect() و next() و rewrite()- middleware.ts يعالج المصادقة والتسجيل و CORS بشكل موحد قبل وصول الطلبات إلى الصفحات أو APIs
- إعداد
matcherيتحكم في نطاق مطابقة المسارات لـ middleware - تكوين CORS عبر رؤوس الاستجابة في middleware أو route handlers
- تحديد المعدل: يُنفذ باستخدام خريطة في الذاكرة أو Redis (يوصى بـ Upstash Ratelimit لبيئات الإنتاج)
📝 تمارين
-
تمرين أساسي (⭐): أنشئ
app/api/health/route.tsلإرجاع معلومات فحص الصحة بتنسيق JSON (الحالة، الطابع الزمني، وقت التشغيل). أضف middleware لتسجيل كل استدعاء API وزمن استجابته. -
تمرين متقدم (⭐⭐): أنشئ API كامل لـ CRUD:
app/api/books/route.ts(GET للقائمة + POST للإنشاء) +app/api/books/[id]/route.ts(GET لعرض التفاصيل + PUT للتحديث + DELETE للحذف). استخدم Zod للتحقق من صحة جسم الطلب. أضف مصادقة API key في middleware. -
تحدي (⭐⭐⭐): نفذ خدمة "اختصار الروابط" API:
app/api/shorten/route.ts(POST يستقبل URL ويُعيد رابطًا مختصرًا)،app/api/[code]/route.ts(GET يسترد رابطًا مختصرًا ويُجري إعادة توجيه 302). أضف تحديد المعدل (10 روابط مختصرة في الدقيقة لكل IP)، ودعم CORS، وسجلات وصول middleware. خزّن الرموز المختصرة في ملف JSON أو خريطة في الذاكرة.