در این مقاله قصد داریم بررسی کنیم که چگونه میتوانیم با استفاده از Next.js بهسادگی یک API بسازیم. مباحثی که در این مقاله به آنها پرداختهایم شامل راهاندازی پروژه، درک App Router و Route Handlerها، مدیریت چندین متد HTTP، پیادهسازی مسیریابی داینامیک، ایجاد منطق middleware با قابلیت استفاده مجدد و تصمیمگیری در مورد زمان راهاندازی یک لایه API اختصاصی میباشد.
۱- شروع کار
۱-۱- ایجاد یک اپلیکیشن Next.js
با استفاده از دستور زیر میتوانیم یک پروژه جدید Next.js را ایجاد کنیم:
npx create-next-app@latest --api
npx create-next-app@latest --api
npx create-next-app@latest --api
نکتهای که باید به آن توجه داشته باشیم این است که استفاده از flag
--api
--api
به صورت خودکار یک فایل
route.ts
route.ts
نمونه در پوشه
app/
app/
پروژه ما اضافه میکند که نحوه ایجاد یک API endpoint را نشان میدهد.
۱-۲- مقایسه App Router و Pages Router
Pages Router: در نسخههای قدیمیتر Next.js، برای ایجاد API از مسیر
pages/api/*
pages/api/*
استفاده میشد. این روش بر پایه Node.js request/response و یک API شبیه به Express کار میکرد.
App Router (روش پیشفرض): از نسخه Next.js 13 به بعد، App Router معرفی شد که کاملاً از استانداردهای Request/Response API در وب پیروی میکند. در این روش، به جای
pages/api/*
pages/api/*
میتوانیم فایلهای
route.ts
route.ts
یا
route.js
route.js
را در هر جایی داخل پوشه
app/
app/
قرار دهیم.
چرا باید از App Router استفاده کنیم؟
Route Handlerها در App Router به جای APIهای مخصوص Node.js، از استانداردهای Web Platform Request/Response استفاده میکنند. این تغییر باعث سادگی یادگیری، کاهش پیچیدگی و امکان استفاده مجدد از دانش ما در ابزارهای مختلف میشود.
۲- چرا و چه زمانی باید با Next.js یک API بسازیم؟
۱- ایجاد یک API عمومی برای چندین کلاینت
ما میتوانیم یک API عمومی ایجاد کنیم که توسط وب اپلیکیشن Next.js، یک اپلیکیشن موبایل مستقل یا حتی سرویسهای third-party مصرف شود. برای مثال، میتوانیم از مسیر
/api/users
/api/users
هم در یک وبسایت
React و هم در یک اپلیکیشن موبایل React Native داده دریافت نماییم.
۲- ایجاد یک لایه واسط برای بکاند موجود
گاهی اوقات نیاز داریم که یک لایه میانی برای مخفی کردن یا یکپارچهسازی چندین microservice خارجی ایجاد کنیم. Route Handlerها در Next.js میتوانند نقش یک proxy را داشته باشند و درخواستها را قبل از ارسال به سرور مقصد، پردازش کنند. به عنوان مثال، میتوانیم درخواستها را رهگیری، احراز هویت، تبدیل دادهها و سپس به یک API دیگر ارسال نماییم.
۳- مدیریت Webhookها و یکپارچهسازیها
اگر نیاز به دریافت Webhookها یا Callbackهای خارجی از سرویسهایی مانند Stripe، GitHub، Twilio و غیره داریم، میتوانیم از Route Handlerها استفاده کنیم.
۴- پیادهسازی سیستم احراز هویت سفارشی
اگر نیاز به مدیریت sessionها، توکن یا هر نوع مکانیزم احراز هویت داریم، میتوانیم در لایه API خود کوکیها را ذخیره کنیم، هدرها را بخوانیم و پاسخ مناسب را ارسال نماییم.
نکتهای که وجود دارد این است، اگر فقط به fetch کردن دادهها در سرور برای اپلیکیشن Next.js خود نیاز داریم و قصد اشتراکگذاری آن را با سایر کلاینتها نداریم، میتوانیم مستقیماً از سرور کامپوننتها برای دریافت دادهها در هنگام رندر استفاده کنیم و نیازی به ایجاد یک لایه API جداگانه نخواهیم داشت.
۳- ایجاد API با Route Handlerها
۳-۱- تنظیمات اولیه فایلها
در App Router (داخل پوشه
app/
app/
)، یک پوشه برای مسیر مورد نظر ایجاد میکنیم و یک فایل
route.ts
route.ts
داخل آن قرار میدهیم.
مثلاً برای ایجاد یک endpoint در مسیر
/api/users
/api/users
، پوشه و فایل به این شکل خواهد بود:
app
└── api
└── users
└── route.ts
app
└── api
└── users
└── route.ts
۳-۲- مدیریت چندین متد HTTP در یک فایل
برخلاف روش Pages Router API که فقط یک default export داشت، در App Router میتوانیم چندین تابع export کنیم و متدهای HTTP مختلف را در یک فایل مدیریت نماییم:
// app/api/users/route.ts
export async function GET(request: Request) {
// For example, fetch data from your DB here
{ id: 1, name: 'Alice' },
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' }
export async function POST(request: Request) {
// Parse the request body
const body = await request.json();
// e.g. Insert new user into your DB
const newUser = { id: Date.now(), name };
return new Response(JSON.stringify(newUser), {
headers: { 'Content-Type': 'application/json' }
// app/api/users/route.ts
export async function GET(request: Request) {
// For example, fetch data from your DB here
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
return new Response(JSON.stringify(users), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
export async function POST(request: Request) {
// Parse the request body
const body = await request.json();
const { name } = body;
// e.g. Insert new user into your DB
const newUser = { id: Date.now(), name };
return new Response(JSON.stringify(newUser), {
status: 201,
headers: { 'Content-Type': 'application/json' }
});
}
// app/api/users/route.ts
export async function GET(request: Request) {
// For example, fetch data from your DB here
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
return new Response(JSON.stringify(users), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
export async function POST(request: Request) {
// Parse the request body
const body = await request.json();
const { name } = body;
// e.g. Insert new user into your DB
const newUser = { id: Date.now(), name };
return new Response(JSON.stringify(newUser), {
status: 201,
headers: { 'Content-Type': 'application/json' }
});
}
اکنون:
- ارسال یک درخواست
GET
GET
به /api/users
/api/users
لیست کاربران را return میکند.
- ارسال یک درخواست
POST
POST
به /api/users
/api/users
یک کاربر جدید را اضافه مینماید.
۴- کار با Web APIها
۴-۱- استفاده مستقیم از Request و Response
به صورت پیشفرض، متدهای Route Handler مانند
GET
GET
و
POST
POST
یک آبجکت
Request استاندارد دریافت میکنند و باید یک آبجکت
Response استاندارد را return کنند.
۴-۲- پارامترهای کوئری
// app/api/search/route.ts
import { NextRequest } from 'next/server';
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get('query'); // e.g. `/api/search?query=hello`
JSON.stringify({ result: `You searched for: ${query}` }),
headers: { 'Content-Type': 'application/json' },
// app/api/search/route.ts
import { NextRequest } from 'next/server';
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get('query'); // e.g. `/api/search?query=hello`
return new Response(
JSON.stringify({ result: `You searched for: ${query}` }),
{
headers: { 'Content-Type': 'application/json' },
},
);
}
// app/api/search/route.ts
import { NextRequest } from 'next/server';
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get('query'); // e.g. `/api/search?query=hello`
return new Response(
JSON.stringify({ result: `You searched for: ${query}` }),
{
headers: { 'Content-Type': 'application/json' },
},
);
}
۴-۳- مدیریت هدرها و کوکیها
Next.js دو تابع کمکی
cookies()
cookies()
و
headers()
headers()
را ارائه میدهد که برای اشتراکگذاری منطق میان کدهای سمت سرور بسیار مفید هستند.
import { NextRequest } from 'next/server';
import { cookies, headers } from 'next/headers';
export async function GET(request: NextRequest) {
// ۱٫ Using 'next/headers' helpers
const cookieStore = await cookies();
const token = cookieStore.get('token');
const headersList = await headers();
const referer = headersList.get('referer');
// ۲٫ Using the standard Web APIs
const userAgent = request.headers.get('user-agent');
return new Response(JSON.stringify({ token, referer, userAgent }), {
headers: { 'Content-Type': 'application/json' },
// app/api/auth/route.ts
import { NextRequest } from 'next/server';
import { cookies, headers } from 'next/headers';
export async function GET(request: NextRequest) {
// ۱٫ Using 'next/headers' helpers
const cookieStore = await cookies();
const token = cookieStore.get('token');
const headersList = await headers();
const referer = headersList.get('referer');
// ۲٫ Using the standard Web APIs
const userAgent = request.headers.get('user-agent');
return new Response(JSON.stringify({ token, referer, userAgent }), {
headers: { 'Content-Type': 'application/json' },
});
}
// app/api/auth/route.ts
import { NextRequest } from 'next/server';
import { cookies, headers } from 'next/headers';
export async function GET(request: NextRequest) {
// ۱٫ Using 'next/headers' helpers
const cookieStore = await cookies();
const token = cookieStore.get('token');
const headersList = await headers();
const referer = headersList.get('referer');
// ۲٫ Using the standard Web APIs
const userAgent = request.headers.get('user-agent');
return new Response(JSON.stringify({ token, referer, userAgent }), {
headers: { 'Content-Type': 'application/json' },
});
}
علاوه بر این، Next.js دو آبجکت
NextRequest
NextRequest
و
NextResponse
NextResponse
را ارائه میدهد که نسخههای پیشرفتهای از Web APIهای بیسیک هستند و امکانات بیشتری ارائه میدهند.
۵- مسیرهای داینامیک
برای ایجاد مسیرهای داینامیک (مثلاً
/api/users/:id
/api/users/:id
) باید از Dynamic Segmentها در ساختار پوشهها استفاده کنیم. به عنوان مثال:
app
└── api
└── users
└── [id]
└── route.ts
app
└── api
└── users
└── [id]
└── route.ts
کدی که داخل
route.ts
route.ts
قرار دارد:
// app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
export async function GET(
{ params }: { params: Promise<{ id: string }> },
const id = (await params).id;
// e.g. Query a database for user with ID `id`
return new Response(JSON.stringify({ id, name: `User ${id}` }), {
headers: { 'Content-Type': 'application/json' },
export async function DELETE(
{ params }: { params: Promise<{ id: string }> },
const id = (await params).id;
// e.g. Delete user with ID `id` in DB
return new Response(null, { status: 204 });
// app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
// e.g. Query a database for user with ID `id`
return new Response(JSON.stringify({ id, name: `User ${id}` }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
// e.g. Delete user with ID `id` in DB
return new Response(null, { status: 204 });
}
// app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
// e.g. Query a database for user with ID `id`
return new Response(JSON.stringify({ id, name: `User ${id}` }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const id = (await params).id;
// e.g. Delete user with ID `id` in DB
return new Response(null, { status: 204 });
}
این فایل به آدرسی مثل
/api/users/123
/api/users/123
مرتبط است، جایی که مقدار
۱۲۳
۱۲۳
به عنوان پارامتر دریافت میشود. مقدار
params.id
params.id
همان بخش داینامیک مسیر را در اختیار ما قرار میدهد.
۶- استفاده از Next.js به عنوان یک لایه proxy یا Forwarding Layer
یک سناریوی رایج این است که Next.js را به عنوان proxy برای یک سرویس بکاند موجود استفاده کنیم. این روش به ما اجازه میدهد تا:
- درخواستها را احراز هویت کنیم.
- لاگگیری انجام دهیم.
- دادهها را قبل از ارسال به سرور، تبدیل و فیلتر نماییم.
مثلا:
// app/api/external/route.ts
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const response = await fetch('https://example.com/api/data', {
// Optional: forward some headers, add auth tokens, etc.
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
// Transform or forward the response
const data = await response.json();
const transformed = { ...data, source: 'proxied-through-nextjs' };
return new Response(JSON.stringify(transformed), {
headers: { 'Content-Type': 'application/json' },
// app/api/external/route.ts
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const response = await fetch('https://example.com/api/data', {
// Optional: forward some headers, add auth tokens, etc.
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
});
// Transform or forward the response
const data = await response.json();
const transformed = { ...data, source: 'proxied-through-nextjs' };
return new Response(JSON.stringify(transformed), {
headers: { 'Content-Type': 'application/json' },
});
}
// app/api/external/route.ts
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const response = await fetch('https://example.com/api/data', {
// Optional: forward some headers, add auth tokens, etc.
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
});
// Transform or forward the response
const data = await response.json();
const transformed = { ...data, source: 'proxied-through-nextjs' };
return new Response(JSON.stringify(transformed), {
headers: { 'Content-Type': 'application/json' },
});
}
حالا کلاینتها تنها کافی است
/api/external
/api/external
را صدا بزنند و Next.js درخواست را مدیریت میکند.
این روش گاهی با عنوان «Backend for Frontend» یا BFF نیز شناخته میشود.
۷- ساخت منطق «Middleware» مشترک
اگر بخواهیم منطق یکسانی (مثلاً احراز هویت، لاگگیری و غیره) را در چندین Route Handler اعمال کنیم، میتوانیم توابع قابل استفاده مجدد بسازیم که Handlerهای ما را wrap کنند:
import { NextRequest } from 'next/server';
type Handler = (req: NextRequest, context?: any) => Promise<Response>;
export function withAuth(handler: Handler): Handler {
return async (req, context) => {
const token = req.cookies.get('token')?.value;
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
headers: { 'Content-Type': 'application/json' },
// If authenticated, call the original handler
return handler(req, context);
// lib/with-auth.ts
import { NextRequest } from 'next/server';
type Handler = (req: NextRequest, context?: any) => Promise<Response>;
export function withAuth(handler: Handler): Handler {
return async (req, context) => {
const token = req.cookies.get('token')?.value;
if (!token) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
// If authenticated, call the original handler
return handler(req, context);
};
}
// lib/with-auth.ts
import { NextRequest } from 'next/server';
type Handler = (req: NextRequest, context?: any) => Promise<Response>;
export function withAuth(handler: Handler): Handler {
return async (req, context) => {
const token = req.cookies.get('token')?.value;
if (!token) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
// If authenticated, call the original handler
return handler(req, context);
};
}
کد Route Handler:
// app/api/secret/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@/lib/with-auth';
async function secretGET(request: NextRequest) {
return new Response(JSON.stringify({ secret: 'Here be dragons' }), {
headers: { 'Content-Type': 'application/json' },
export const GET = withAuth(secretGET);
// app/api/secret/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@/lib/with-auth';
async function secretGET(request: NextRequest) {
return new Response(JSON.stringify({ secret: 'Here be dragons' }), {
headers: { 'Content-Type': 'application/json' },
});
}
export const GET = withAuth(secretGET);
// app/api/secret/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@/lib/with-auth';
async function secretGET(request: NextRequest) {
return new Response(JSON.stringify({ secret: 'Here be dragons' }), {
headers: { 'Content-Type': 'application/json' },
});
}
export const GET = withAuth(secretGET);
۸- دیپلوی و ملاحظات مربوط به «SPA Mode»
۸-۱- دیپلوی استاندارد روی Node.js
وقتی Next.js را با
next start
next start
روی یک سرور Node.js اجرا کنیم، میتوانیم از قابلیتهایی مانند Route Handlerها، Server Componentها، Middleware و غیره بهرهمند شویم.
هیچ تنظیم اضافهای لازم نیست. برای جزئیات بیشتر مطالعه مستندات دیپلوی Next.js میتواند مفید باشد.
۸-۲- Export گرفتن به صورت SPA / Static
Next.js امکان ایجاد یک سایت کاملاً استاتیک را نیز فراهم میکند، که در آن کل سایت به عنوان یک Single-Page Application (SPA) ساخته میشود.
برای فعال کردن این حالت، در next.config.js مقدار output را برابر “export” قرار میدهیم:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
export default nextConfig;
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'export',
};
export default nextConfig;
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'export',
};
export default nextConfig;
نکات مهمی که وجود دارد عبارتند از:
- در حالت SPA، کدهای سروری اجرا نمیشوند (یعنی APIها کار نخواهند کرد).
- اگر به API نیاز داریم، باید آن را جداگانه روی یک سرور مستقل (مثلاً یک سرور Node.js) هاست کنیم.
- Route Handlerهای GET فقط در صورتی میتوانند استاتیک شوند که به دادههای داینامیک نیاز نداشته باشند.
۸-۳- دیپلوی APIهای Next.js روی Vercel
اگر برنامه خود را روی Vercel دیپلوی میکنیم، Next.js از امکانات ویژهای مانند Rate-limiting برنامهنویسی شده با استفاده از Vercel Firewall و اجرای Cron Jobs برای پردازشهای زمانبندی شده پشتیبانی میکند. برای جزئیات بیشتر، بررسی راهنمای دیپلوی APIها روی Vercel میتواند مفید باشد.
۹- چه زمانی نیازی به API Endpoint نداریم؟
با استفاده از سرور کامپوننتهای React در App Router، میتوانیم دادهها را مستقیماً روی سرور دریافت کنیم، بدون اینکه API عمومی ایجاد نماییم. به عنوان مثال:
export default async function UsersPage() {
// This fetch runs on the server (no client-side code needed here)
const res = await fetch('https://api.example.com/users');
const data = await res.json();
{data.map((user: any) => (
<li key={user.id}>{user.name}</li>
// app/users/page.tsx
// (Server Component)
export default async function UsersPage() {
// This fetch runs on the server (no client-side code needed here)
const res = await fetch('https://api.example.com/users');
const data = await res.json();
return (
<main>
<h1>Users</h1>
<ul>
{data.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</main>
);
}
// app/users/page.tsx
// (Server Component)
export default async function UsersPage() {
// This fetch runs on the server (no client-side code needed here)
const res = await fetch('https://api.example.com/users');
const data = await res.json();
return (
<main>
<h1>Users</h1>
<ul>
{data.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</main>
);
}
اگر دادههای ما فقط داخل Next.js استفاده میشود، نیازی به ایجاد API عمومی نداریم.
۱۰- جمعبندی
مراحل نهایی برای ایجاد API در Next.js عبارتند از:
- یک پروژه جدید میسازیم:
npx create-next-app@latest --api
npx create-next-app@latest --api
- Route Handlerها را داخل دایرکتوری
app/
app/
اضافه میکنیم (مثلاً app/api/users/route.ts
app/api/users/route.ts
).
- متدهایHTTP (مثل
GET
GET
, POST
POST
, PUT
PUT
, DELETE
DELETE
) را در همان فایل export میکنیم.
- از Web Standard APIها برای پردازش
Request
Request
و Response
Response
استفاده میکنیم.
- اگر نیاز به API عمومی داریم، یک Route Handler میسازیم؛ در غیر این صورت، دادهها را مستقیماً در سرور کامپوننت دریافت مینماییم.
- مسیرهای API جدید خود را از سمت کلاینت دریافت میکنیم (مثلاً داخل یک کلاینت کامپوننت یا با استفاده از
fetch('/api/...')
fetch('/api/...')
).
- یا اگر یک سرور کامپوننت میتواند دادهها را مستقیماً دریافت کند، نیازی به ایجاد API Route جداگانه نداریم.
- Middleware مشترک (مثلاً
withAuth()
withAuth()
) برای احراز هویت یا منطق تکراری میسازیم.
- اگر به قابلیتهای سروری نیاز داریم، روی Node.js دیپلوی میکنیم؛ در غیر این صورت، اگر سایت ما فقط استاتیک است، خروجی SPA میگیریم.
نتیجهگیری
استفاده از App Router و Route Handlerها در Next.js به ما یک روش مدرن و انعطافپذیر برای ساخت API میدهد که کاملاً با Web Platform هماهنگ است. با این قابلیتها میتوانیم:
- یک API عمومی برای استفاده در وب، موبایل یا کلاینتهای third-party ایجاد کنیم.
- درخواستها را به سرویسهای خارجی هدایت کرده و شخصیسازی نماییم.
- یک لایه Middleware قابل استفاده مجدد برای احراز هویت، لاگگیری یا منطقهای تکراری دیگر پیادهسازی کنیم.
- مسیرهای داینامیک را با استفاده از ساختار پوشهای
[id]
[id]
مدیریت کنیم.
دیدگاهها: