Axios چیست؟ آموزش کامل Axios و Fetch برای اتصال JavaScript و React به API هوش مصنوعی
در این آموزش Axios را از صفر تا سطح کاربردی یاد میگیرید؛ از GET و POST تا Instance، Interceptor، Timeout، لغو درخواست و مدیریت خطا. در پایان یک کلاینت امن برای اتصال React و Node.js به API هوش مصنوعی درواره میسازیم.
اگر در JavaScript، React یا Node.js با API کار کرده باشید، احتمالاً نام Axios را دیدهاید. Axios یکی از کتابخانههای شناختهشده برای ارسال درخواست HTTP است و بسیاری از توسعهدهندگان از آن برای ارتباط Frontend یا Backend با REST API استفاده میکنند.
با Axios میتوانید درخواستهای GET، POST، PUT، PATCH و DELETE ارسال کنید، Header بسازید، Query Parameter تعریف کنید، Timeout قرار دهید، درخواست را لغو کنید و خطاهای سرور را بهصورت متمرکز مدیریت کنید.
اما JavaScript از قبل fetch دارد. پس چرا باید Axios نصب کنیم؟ تفاوت Axios و Fetch چیست؟ Axios Instance و Interceptor چه کاربردی دارند؟ چگونه باید یک کلاینت API قابل نگهداری بسازیم؟ و مهمتر از همه، آیا میتوان API Key هوش مصنوعی را مستقیماً در React قرار داد؟
در این راهنما، Axios را از مفاهیم پایه تا پیادهسازی یک پروژه واقعی بررسی میکنیم. در پایان، یک رابط React میسازیم که از طریق Backend امن Node.js به API هوش مصنوعی درواره متصل میشود.
Axios چیست؟
Axios یک HTTP Client مبتنی بر Promise برای JavaScript است. این کتابخانه در مرورگر و محیطهای Server-side مانند Node.js قابلاستفاده است.
با Axios میتوان کارهای زیر را انجام داد:
- دریافت اطلاعات از API
- ارسال JSON به سرور
- ارسال Form Data
- تنظیم Header
- استفاده از Query Parameter
- تعیین Timeout
- لغو درخواست با
AbortController - مدیریت متمرکز Request و Response
- ساخت چند Axios Instance
- تبدیل خودکار پاسخ JSON
- بررسی Status Code
- مدیریت خطاهای شبکه و سرور
- نمایش Progress ارسال یا دریافت فایل در محیطهای پشتیبانیشده
براساس مستندات رسمی Axios، این کتابخانه یک HTTP Client مبتنی بر Promise است و با Package Managerهایی مانند npm، pnpm، Yarn و Bun نصب میشود.
HTTP Request چیست؟
هنگامی که Frontend، اپلیکیشن موبایل یا Backend به یک API متصل میشود، معمولاً یک HTTP Request ارسال میکند.
هر Request میتواند شامل اجزای زیر باشد:
- URL
- Method
- Header
- Query Parameter
- Request Body
- Timeout
- اطلاعات احراز هویت
نمونه درخواست:
POST /api/chat HTTP/1.1
Host: example.com
Content-Type: application/json
{
"message": "Axios چیست؟"
}
سرور نیز یک HTTP Response برمیگرداند:
HTTP/1.1 200 OK
Content-Type: application/json
{
"answer": "Axios یک HTTP Client برای JavaScript است."
}
Axios فرایند ساخت Request، دریافت Response و مدیریت خطا را سادهتر میکند.
نصب Axios
برای نصب Axios با npm اجرا کنید:
npm install axios
با pnpm:
pnpm add axios
با Yarn:
yarn add axios
با Bun:
bun add axios
سپس آن را Import کنید:
import axios from "axios";
در پروژههای CommonJS:
const axios = require("axios");
روش Import ممکن است به تنظیمات پروژه و نوع Module آن بستگی داشته باشد. در پروژههای جدید Vite، React و Node.js معمولاً از ES Modules استفاده میشود.
اولین درخواست GET با Axios
برای دریافت اطلاعات:
import axios from "axios";
async function getPost() {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts/1"
);
console.log(response.data);
}
getPost();
Axios یک Response Object برمیگرداند. داده اصلی معمولاً در response.data قرار دارد.
ساختار متداول پاسخ Axios:
{
data: {},
status: 200,
statusText: "OK",
headers: {},
config: {},
request: {}
}
بنابراین برای استفاده از داده:
const response = await axios.get("/api/users");
console.log(response.data);
console.log(response.status);
console.log(response.headers);
ارسال Query Parameter
فرض کنید API از پارامترهای page و limit استفاده میکند:
/api/posts?page=2&limit=10
بهجای ساخت دستی URL، از گزینه params استفاده کنید:
const response = await axios.get("/api/posts", {
params: {
page: 2,
limit: 10,
},
});
console.log(response.data);
Axios پارامترها را به Query String تبدیل میکند.
برای جستوجو:
const response = await axios.get("/api/search", {
params: {
q: "آموزش هوش مصنوعی",
category: "programming",
},
});
از چسباندن مستقیم ورودی کاربر به URL خودداری کنید:
const url = "/api/search?q=" + userInput;
استفاده از params خواناتر است و Encoding مقادیر را بهتر مدیریت میکند.
ارسال درخواست POST
برای ارسال اطلاعات JSON:
import axios from "axios";
async function createPost() {
const response = await axios.post("/api/posts", {
title: "آموزش Axios",
content: "محتوای مقاله",
});
console.log(response.data);
}
آرگومان دوم axios.post بدنه Request است.
شکل کلی:
axios.post(url, data, config);
مثال همراه با Header:
const response = await axios.post(
"/api/posts",
{
title: "آموزش API",
},
{
headers: {
"Content-Type": "application/json",
},
}
);
Axios هنگام ارسال یک Object معمولی، معمولاً آن را به JSON تبدیل میکند و Header مناسب را تنظیم میکند. بااینحال در پروژههای واقعی بهتر است ساختار Request را صریح و قابلپیشبینی نگه دارید.
متدهای HTTP در Axios
Axios برای متدهای متداول HTTP میانبر دارد:
axios.get(url, config);
axios.post(url, data, config);
axios.put(url, data, config);
axios.patch(url, data, config);
axios.delete(url, config);
axios.head(url, config);
axios.options(url, config);
GET
برای دریافت Resource:
const response = await axios.get("/api/users/42");
POST
برای ساخت Resource یا اجرای عملیات:
const response = await axios.post("/api/users", {
name: "Amir",
});
PUT
معمولاً برای جایگزینی کامل Resource:
const response = await axios.put("/api/users/42", {
name: "Amir",
role: "admin",
});
PATCH
برای بهروزرسانی بخشی از Resource:
const response = await axios.patch("/api/users/42", {
name: "Amir Afshar",
});
DELETE
برای حذف Resource:
const response = await axios.delete("/api/users/42");
معنای دقیق هر Method باید براساس قرارداد API مشخص شود. همه APIها الزاماً از الگوی یکسانی پیروی نمیکنند.
ارسال Header با Axios
Headerها اطلاعات تکمیلی Request را منتقل میکنند.
const response = await axios.get("/api/profile", {
headers: {
Accept: "application/json",
"X-Client-Version": "1.0.0",
},
});
برای احراز هویت Bearer Token:
const response = await axios.get("/api/profile", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
اگر این کد در Frontend اجرا میشود، فقط Token کاربر را براساس معماری احراز هویت برنامه مدیریت کنید. API Key خصوصی یک سرویس زیرساختی نباید داخل JavaScript مرورگر قرار بگیرد.
پیکربندی Request با axios.request
میتوانید تمام تنظیمات را در یک Object قرار دهید:
const response = await axios.request({
method: "POST",
url: "/api/chat",
headers: {
"Content-Type": "application/json",
},
data: {
message: "Axios را توضیح بده.",
},
timeout: 15000,
});
این روش برای توابع عمومی یا زمانی که Method بهصورت پویا تعیین میشود مناسب است.
تفاوت data و params در Axios
یکی از اشتباهات متداول، اشتباه گرفتن data و params است.
params
در Query String قرار میگیرد:
const response = await axios.get("/api/models", {
params: {
category: "text",
},
});
آدرس نهایی تقریباً چنین خواهد بود:
/api/models?category=text
data
در Request Body قرار میگیرد:
const response = await axios.post("/api/chat", {
message: "سلام",
});
در تنظیمات کامل:
const response = await axios.request({
method: "POST",
url: "/api/chat",
params: {
language: "fa",
},
data: {
message: "سلام",
},
});
در این مثال language داخل URL و message داخل Body قرار میگیرد.
مدیریت خطا در Axios
درخواست HTTP ممکن است به دلایل مختلف شکست بخورد:
- سرور Status Code خطا برگرداند.
- اینترنت کاربر قطع باشد.
- درخواست Timeout شود.
- DNS یا SSL مشکل داشته باشد.
- Request قبل از ارسال کامل دچار خطا شود.
- ساختار Response با انتظار برنامه یکسان نباشد.
یک الگوی مناسب:
import axios from "axios";
async function loadData() {
try {
const response = await axios.get("/api/data");
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
console.error("Server error:", {
status: error.response.status,
data: error.response.data,
});
} else if (error.request) {
console.error("No response received.");
} else {
console.error("Request configuration error:", error.message);
}
} else {
console.error("Unexpected error:", error);
}
throw error;
}
}
براساس API Reference رسمی Axios، تابع axios.isAxiosError برای تشخیص امن خطاهای اختصاصی Axios قابلاستفاده است.
تفاوت error.response و error.request
error.response
Request ارسال شده و سرور Response برگردانده است، اما Status Code در محدوده موفقیت موردانتظار Axios نبوده است:
if (error.response) {
console.log(error.response.status);
console.log(error.response.data);
}
مثالها:
400 Bad Request401 Unauthorized403 Forbidden404 Not Found429 Too Many Requests500 Internal Server Error
error.request
Request ساخته و ارسال شده، اما Response قابلاستفادهای دریافت نشده است:
if (error.request) {
console.log("Server did not respond.");
}
علت احتمالی:
- اختلال شبکه
- Timeout
- سرور خارج از دسترس
- مشکل DNS
- محدودیت مرورگر
- تنظیمات نادرست CORS
خطاهای دیگر
ممکن است خطا پیش از ارسال Request اتفاق بیفتد:
console.log(error.message);
ساخت پیام خطای قابلفهم برای کاربر
پیام فنی Axios را مستقیماً به کاربر نمایش ندهید. یک تابع تبدیل خطا بسازید:
import axios from "axios";
export function getErrorMessage(error) {
if (!axios.isAxiosError(error)) {
return "خطای پیشبینینشدهای رخ داد.";
}
if (error.code === "ECONNABORTED") {
return "زمان انتظار درخواست به پایان رسید.";
}
if (!error.response) {
return "ارتباط با سرور برقرار نشد.";
}
switch (error.response.status) {
case 400:
return error.response.data?.error || "اطلاعات ارسالشده معتبر نیست.";
case 401:
return "برای ادامه باید وارد حساب کاربری شوید.";
case 403:
return "اجازه انجام این عملیات را ندارید.";
case 404:
return "منبع موردنظر پیدا نشد.";
case 429:
return "تعداد درخواستها زیاد است؛ کمی بعد دوباره تلاش کنید.";
case 500:
case 502:
case 503:
case 504:
return "سرویس موقتاً در دسترس نیست.";
default:
return "درخواست با خطا روبهرو شد.";
}
}
Backend بهتر است یک ساختار خطای ثابت ارائه کند:
{
"error": "پیام قابلنمایش",
"code": "INVALID_INPUT"
}
تعیین Timeout
بدون Timeout مناسب، یک Request ممکن است مدت زیادی منتظر بماند.
const response = await axios.get("/api/data", {
timeout: 10000,
});
مقدار Timeout برحسب میلیثانیه است:
10000 = 10 seconds
برای یک Axios Instance:
const api = axios.create({
baseURL: "/api",
timeout: 15000,
});
در صورت پایان زمان انتظار، Axios میتواند خطایی مانند ECONNABORTED ایجاد کند:
try {
await api.get("/reports");
} catch (error) {
if (axios.isAxiosError(error) && error.code === "ECONNABORTED") {
console.log("Request timed out.");
}
}
مستندات Axios توصیه میکند در محیط Production برای Requestها Timeout تعریف شود تا درخواست متوقفشده بدون محدودیت منتظر نماند. جزئیات آن در راهنمای شروع Axios آمده است.
لغو درخواست با AbortController
ممکن است کاربر صفحه را ترک کند، جستوجوی جدیدی انجام دهد یا دکمه توقف را بزند. در این شرایط بهتر است Request قبلی لغو شود.
import axios from "axios";
const controller = new AbortController();
async function loadData() {
try {
const response = await axios.get("/api/data", {
signal: controller.signal,
});
console.log(response.data);
} catch (error) {
if (axios.isCancel(error)) {
console.log("Request was canceled.");
return;
}
throw error;
}
}
loadData();
controller.abort();
روش قدیمی CancelToken منسوخ شده و برای پروژههای جدید استفاده از AbortController توصیه میشود. این موضوع در API Reference رسمی Axios نیز ذکر شده است.
لغو خودکار درخواست جستوجوی قبلی
در جستوجوی لحظهای، کاربر ممکن است سریع تایپ کند. بهتر است Request قبلی لغو شود:
import axios from "axios";
let activeController;
export async function searchArticles(query) {
activeController?.abort();
activeController = new AbortController();
const response = await axios.get("/api/search", {
params: {
q: query,
},
signal: activeController.signal,
});
return response.data;
}
در پروژههای واقعی میتوانید علاوه بر لغو Request، از Debounce نیز استفاده کنید تا برای هر کلید فشردهشده درخواست جدید ساخته نشود.
Axios Instance چیست؟
اگر در چند فایل از یک API استفاده میکنید، تکرار baseURL، Header و Timeout باعث شلوغی کد میشود.
Axios Instance یک کلاینت پیکربندیشده و قابلاستفاده مجدد است:
import axios from "axios";
export const api = axios.create({
baseURL: "/api",
timeout: 15000,
headers: {
Accept: "application/json",
},
});
حالا Requestها سادهتر میشوند:
const response = await api.get("/users");
const response = await api.post("/chat", {
message: "سلام",
});
ساخت Instance برای پروژههای متوسط و بزرگ بسیار مهم است، زیرا تنظیمات مشترک را از کامپوننتهای رابط کاربری جدا میکند.
ساخت چند Axios Instance
ممکن است یک برنامه به چند سرویس متصل شود:
import axios from "axios";
export const internalApi = axios.create({
baseURL: "/api",
timeout: 10000,
});
export const analyticsApi = axios.create({
baseURL: "https://analytics.example.com",
timeout: 5000,
});
بهتر است برای سرویسهای متفاوت Instanceهای جداگانه داشته باشید. این کار از ارسال اشتباه Header یا Token یک سرویس به سرویس دیگر جلوگیری میکند.
Axios Interceptor چیست؟
Interceptor به شما اجازه میدهد قبل از ارسال Request یا پس از دریافت Response، عملیات مشترکی انجام دهید.
کاربردهای متداول:
- افزودن Access Token
- افزودن شناسه Request
- ثبت مدت زمان Request
- تبدیل Response
- مدیریت متمرکز خطا
- هدایت کاربر پس از خطای
401 - ثبت Log در محیط توسعه
Request Interceptor
api.interceptors.request.use(
(config) => {
const accessToken = getAccessToken();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
Response Interceptor
api.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response?.status === 401) {
console.log("Authentication is required.");
}
return Promise.reject(error);
}
);
Interceptor باید خطا را با Promise.reject ادامه دهد؛ در غیر این صورت ممکن است بخش فراخواننده تصور کند Request موفق بوده است.
اطلاعات مربوط به ثبت Interceptor روی Instance یا Axios عمومی در مستندات رسمی Interceptors ارائه شده است.
اشتباهات رایج در Interceptor
ثبت چندباره Interceptor در React
این کد اگر داخل کامپوننت و در هر Render اجرا شود، چند Interceptor تکراری میسازد:
api.interceptors.request.use((config) => {
return config;
});
بهتر است Interceptorهای عمومی هنگام ساخت API Client ثبت شوند، نه در هر Render کامپوننت.
اگر مجبورید Interceptor را داخل Hook ثبت کنید، هنگام Unmount آن را حذف کنید:
import { useEffect } from "react";
import { api } from "./api";
export function useApiLogging() {
useEffect(() => {
const interceptorId = api.interceptors.response.use(
(response) => response,
(error) => Promise.reject(error)
);
return () => {
api.interceptors.response.eject(interceptorId);
};
}, []);
}
افزودن Token به همه دامنهها
اگر از Axios عمومی استفاده کنید، ممکن است Header احراز هویت ناخواسته به دامنه دیگری ارسال شود.
بهتر است Token را فقط روی Instance متعلق به Backend خودتان تنظیم کنید:
const api = axios.create({
baseURL: "/api",
});
Axios یا Fetch؛ کدام بهتر است؟
Fetch API بخشی از استاندارد Web Platform است و در مرورگرهای مدرن در دسترس قرار دارد. محیطهای جدید Node.js نیز از Fetch پشتیبانی میکنند. برای استفاده از Fetch در این محیطها معمولاً نیازی به نصب کتابخانه جداگانه نیست.
Axios یک کتابخانه مستقل است که قابلیتهایی مانند Instance، Interceptor، Timeout پیکربندیشده و مدیریت خطای متفاوت را ارائه میدهد.
| معیار | Axios | Fetch |
|---|---|---|
| نصب Package | لازم است | معمولاً لازم نیست |
| تبدیل JSON پاسخ | معمولاً در response.data آماده است | باید response.json() اجرا شود |
| رفتار روی 4xx و 5xx | Promise معمولاً Reject میشود | Promise فقط بهدلیل Status Code رد نمیشود |
| Timeout | گزینه مستقیم دارد | معمولاً با AbortSignal پیادهسازی میشود |
| Interceptor | داخلی | باید Wrapper ساخته شود |
| Instance با Base URL | داخلی | باید Wrapper ساخته شود |
| لغو Request | AbortController | AbortController |
| Query Parameter | گزینه params | معمولاً با URLSearchParams |
| اندازه Bundle | Package اضافه میکند | Package اضافه نمیکند |
| استفاده ساده | بسیار مناسب | بسیار مناسب |
| کنترل بدون وابستگی | کمتر | بیشتر |
براساس مستندات MDN درباره Fetch API، Fetch یک رابط JavaScript برای ساخت Request و پردازش Responseهای HTTP فراهم میکند.
تفاوت مهم مدیریت Status Code
Axios معمولاً برای Status Codeهای خارج از محدوده موفقیت، Request را وارد catch میکند:
try {
const response = await axios.get("/api/missing");
} catch (error) {
console.log(error.response?.status);
}
Fetch برای خطاهایی مانند 404 یا 500 الزاماً Promise را Reject نمیکند. باید response.ok را بررسی کنید:
const response = await fetch("/api/missing");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
این تفاوت یکی از مهمترین نکاتی است که هنگام مهاجرت بین Axios و Fetch باید در نظر بگیرید.
نمونه GET با Fetch
async function getUser() {
const response = await fetch("/api/users/42", {
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
معادل تقریبی با Axios:
async function getUser() {
const response = await axios.get("/api/users/42", {
headers: {
Accept: "application/json",
},
});
return response.data;
}
نمونه POST با Fetch
async function createUser(user) {
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user),
});
const data = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(data?.error || "Request failed.");
}
return data;
}
معادل با Axios:
async function createUser(user) {
const response = await axios.post("/api/users", user);
return response.data;
}
چه زمانی Axios انتخاب مناسبی است؟
Axios میتواند انتخاب خوبی باشد اگر:
- پروژه Requestهای زیادی دارد.
- به چند API متصل میشوید.
- به Interceptor نیاز دارید.
- میخواهید Base URL و Header مشترک تعریف کنید.
- مدیریت متمرکز خطا برایتان مهم است.
- تیم از قبل با Axios آشنا است.
- پروژه React، Vue، Angular یا Node.js دارید.
- Timeout و لغو Request باید استاندارد شود.
- به API Client مجزا و قابل تست نیاز دارید.
چه زمانی Fetch کافی است؟
Fetch معمولاً کافی است اگر:
- پروژه کوچک است.
- تعداد Requestها محدود است.
- نمیخواهید Dependency جدید اضافه کنید.
- قابلیتهای استاندارد مرورگر کافی است.
- Wrapper اختصاصی سادهای برای Requestها دارید.
- کنترل مستقیم روی Request و Response میخواهید.
- محیط اجرای شما Fetch را بهصورت داخلی پشتیبانی میکند.
استفاده از Axios یا Fetch بهتنهایی نشانه حرفهایتر بودن پروژه نیست. انتخاب درست به نیاز، معماری و استاندارد تیم بستگی دارد.
پروژه عملی: اتصال React به API هوش مصنوعی با Axios
در این پروژه React مستقیماً به API درواره متصل نمیشود؛ زیرا قرار دادن API Key در Frontend باعث افشای آن میشود.
ساختار امن پروژه:
React و Axios
|
| POST /api/chat
v
Backend با Node.js
|
| API Key خصوصی
v
API هوش مصنوعی درواره
ساخت Frontend با React و Vite
پروژه را بسازید:
npm create vite@latest axios-ai-app -- --template react
cd axios-ai-app
npm install
npm install axios
Development Server را اجرا کنید:
npm run dev
تنظیم Proxy در Vite
فایل vite.config.js:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
},
},
},
});
اکنون Request زیر در محیط توسعه به Backend هدایت میشود:
axios.post("/api/chat", data);
ساخت Axios Client
پوشه زیر را بسازید:
src/lib/
فایل src/lib/api.js:
import axios from "axios";
export const api = axios.create({
baseURL: "/api",
timeout: 30000,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
api.interceptors.request.use(
(config) => {
config.metadata = {
startedAt: Date.now(),
};
return config;
},
(error) => Promise.reject(error)
);
api.interceptors.response.use(
(response) => {
if (import.meta.env.DEV) {
const startedAt = response.config.metadata?.startedAt;
const duration = startedAt ? Date.now() - startedAt : null;
console.log("API request completed:", {
method: response.config.method,
url: response.config.url,
status: response.status,
duration,
});
}
return response;
},
(error) => {
if (import.meta.env.DEV) {
console.error("API request failed:", {
method: error.config?.method,
url: error.config?.url,
status: error.response?.status,
code: error.code,
});
}
return Promise.reject(error);
}
);
اطلاعات حساس مانند Header کامل Authorization یا Body پیام کاربران را در Log مرورگر ثبت نکنید.
ساخت سرویس Chat
فایل src/services/chatService.js:
import axios from "axios";
import { api } from "../lib/api";
export async function sendChatMessage(message, signal) {
try {
const response = await api.post(
"/chat",
{
message,
},
{
signal,
}
);
if (typeof response.data?.answer !== "string") {
throw new Error("ساختار پاسخ معتبر نیست.");
}
return response.data.answer;
} catch (error) {
if (axios.isCancel(error)) {
throw new Error("REQUEST_CANCELED");
}
if (!axios.isAxiosError(error)) {
throw error;
}
if (error.code === "ECONNABORTED") {
throw new Error("زمان انتظار برای دریافت پاسخ به پایان رسید.");
}
if (!error.response) {
throw new Error("ارتباط با سرور برقرار نشد.");
}
throw new Error(
error.response.data?.error ||
"دریافت پاسخ هوش مصنوعی با خطا روبهرو شد."
);
}
}
ساخت رابط React
فایل src/App.jsx:
import { useRef, useState } from "react";
import { sendChatMessage } from "./services/chatService";
import "./App.css";
export default function App() {
const [message, setMessage] = useState("");
const [answer, setAnswer] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const controllerRef = useRef(null);
async function handleSubmit(event) {
event.preventDefault();
const cleanMessage = message.trim();
if (!cleanMessage) {
setError("لطفاً پیام خود را وارد کنید.");
return;
}
if (cleanMessage.length > 4000) {
setError("متن واردشده بیش از حد طولانی است.");
return;
}
controllerRef.current?.abort();
controllerRef.current = new AbortController();
setLoading(true);
setError("");
setAnswer("");
try {
const result = await sendChatMessage(
cleanMessage,
controllerRef.current.signal
);
setAnswer(result);
} catch (requestError) {
if (requestError.message !== "REQUEST_CANCELED") {
setError(
requestError instanceof Error
? requestError.message
: "خطای پیشبینینشدهای رخ داد."
);
}
} finally {
setLoading(false);
controllerRef.current = null;
}
}
function handleCancel() {
controllerRef.current?.abort();
controllerRef.current = null;
setLoading(false);
}
return (
<main className="page">
<section className="chat">
<header>
<span className="badge">React + Axios</span>
<h1>دستیار هوش مصنوعی</h1>
<p>
درخواست شما از طریق Backend امن به API هوش مصنوعی درواره
ارسال میشود.
</p>
</header>
<form onSubmit={handleSubmit}>
<label htmlFor="message">پیام شما</label>
<textarea
id="message"
rows="7"
value={message}
onChange={(event) => setMessage(event.target.value)}
placeholder="برای مثال: تفاوت Axios و Fetch را خلاصه کن."
disabled={loading}
/>
<div className="actions">
<span>{message.length.toLocaleString("fa-IR")} کاراکتر</span>
<div className="buttons">
{loading && (
<button
className="cancel"
type="button"
onClick={handleCancel}
>
توقف
</button>
)}
<button
className="submit"
type="submit"
disabled={loading || !message.trim()}
>
{loading ? "در حال دریافت پاسخ..." : "ارسال"}
</button>
</div>
</div>
</form>
{error && (
<div className="result error" role="alert">
{error}
</div>
)}
{answer && (
<section className="result answer" aria-live="polite">
<h2>پاسخ</h2>
<p>{answer}</p>
</section>
)}
</section>
</main>
);
}
طراحی CSS پروژه
فایل src/App.css:
:root {
font-family:
Vazirmatn, IRANSans, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
color: #192033;
background: #f3f5fb;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
button,
textarea {
font: inherit;
}
.page {
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 16px;
background:
radial-gradient(circle at 10% 10%, #dbe8ff, transparent 34%),
radial-gradient(circle at 90% 90%, #e8ddff, transparent 30%),
#f7f8fc;
}
.chat {
width: min(760px, 100%);
padding: 32px;
border: 1px solid #e0e4ef;
border-radius: 24px;
background: rgba(255, 255, 255, 0.94);
box-shadow: 0 22px 70px rgba(42, 50, 82, 0.12);
}
.chat h1 {
margin: 12px 0 8px;
font-size: clamp(2rem, 6vw, 3rem);
}
.chat header p {
margin: 0;
color: #626b7f;
line-height: 1.9;
}
.badge {
display: inline-flex;
padding: 6px 12px;
border-radius: 999px;
color: #3e5fc5;
background: #e9efff;
font-weight: 700;
}
form {
display: grid;
gap: 12px;
margin-top: 28px;
}
label {
font-weight: 700;
}
textarea {
width: 100%;
resize: vertical;
padding: 16px;
border: 1px solid #cbd1df;
border-radius: 14px;
color: #192033;
background: #fff;
line-height: 1.8;
outline: none;
}
textarea:focus {
border-color: #5270d7;
box-shadow: 0 0 0 4px rgba(82, 112, 215, 0.12);
}
.actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.actions > span {
color: #747d90;
font-size: 0.9rem;
}
.buttons {
display: flex;
gap: 10px;
}
button {
padding: 11px 19px;
border: 0;
border-radius: 11px;
cursor: pointer;
}
button:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.submit {
color: #fff;
background: #4766cc;
}
.cancel {
color: #912d3f;
background: #ffedf0;
}
.result {
margin-top: 20px;
padding: 18px;
border-radius: 14px;
line-height: 1.9;
white-space: pre-wrap;
}
.result h2 {
margin-top: 0;
font-size: 1.1rem;
}
.result p {
margin-bottom: 0;
}
.answer {
background: #eef3ff;
}
.error {
color: #8b2335;
background: #fff0f2;
}
@media (max-width: 600px) {
.chat {
padding: 22px;
border-radius: 18px;
}
.actions {
align-items: stretch;
flex-direction: column;
}
.buttons {
width: 100%;
}
.buttons button {
flex: 1;
}
}
ساخت Backend با Node.js و Express
در ریشه پروژه:
mkdir server
cd server
npm init -y
npm install express axios dotenv
فایل server/package.json:
{
"name": "axios-ai-backend",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "node --watch server.js",
"start": "node server.js"
}
}
تنظیم API Key در Backend
فایل server/.env:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
PORT=3000
شناسه مدل مناسب و قیمت بهروز مدلها را از صفحه مدلهای درواره دریافت کنید.
این فایل را در Git ثبت نکنید:
server/.env
node_modules
dist
ساخت Axios Instance برای API درواره
فایل server/darvarehClient.js:
import axios from "axios";
export const darvarehClient = axios.create({
baseURL: "https://api.darvareh.ir/v1",
timeout: 60000,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
darvarehClient.interceptors.request.use((config) => {
config.headers.Authorization =
`Bearer ${process.env.DARVAREH_API_KEY}`;
config.metadata = {
startedAt: Date.now(),
};
return config;
});
darvarehClient.interceptors.response.use(
(response) => {
const duration =
Date.now() - response.config.metadata.startedAt;
console.log("Darvareh request completed:", {
status: response.status,
duration,
});
return response;
},
(error) => {
const startedAt = error.config?.metadata?.startedAt;
const duration = startedAt ? Date.now() - startedAt : null;
console.error("Darvareh request failed:", {
status: error.response?.status,
code: error.code,
duration,
});
return Promise.reject(error);
}
);
API Key فقط در Backend قرار دارد و از طریق process.env خوانده میشود.
پیادهسازی Backend
فایل server/server.js:
import "dotenv/config";
import express from "express";
import axios from "axios";
import { darvarehClient } from "./darvarehClient.js";
const app = express();
const port = Number(process.env.PORT || 3000);
app.disable("x-powered-by");
app.use(express.json({ limit: "32kb" }));
app.get("/api/health", (request, response) => {
response.json({
status: "ok",
});
});
app.post("/api/chat", async (request, response) => {
const message =
typeof request.body?.message === "string"
? request.body.message.trim()
: "";
if (!message) {
return response.status(400).json({
error: "متن پیام الزامی است.",
code: "MESSAGE_REQUIRED",
});
}
if (message.length > 4000) {
return response.status(400).json({
error: "طول پیام بیشتر از مقدار مجاز است.",
code: "MESSAGE_TOO_LONG",
});
}
if (
!process.env.DARVAREH_API_KEY ||
!process.env.DARVAREH_MODEL_ID
) {
console.error("Darvareh environment variables are missing.");
return response.status(500).json({
error: "تنظیمات سرویس کامل نیست.",
code: "SERVER_CONFIGURATION_ERROR",
});
}
try {
const upstreamResponse = await darvarehClient.post(
"/chat/completions",
{
model: process.env.DARVAREH_MODEL_ID,
messages: [
{
role: "system",
content:
"شما یک دستیار فارسی دقیق و کاربردی هستید. پاسخ را روشن، منظم و مبتنی بر پرسش کاربر بنویسید.",
},
{
role: "user",
content: message,
},
],
temperature: 0.4,
}
);
const answer =
upstreamResponse.data?.choices?.[0]?.message?.content;
if (typeof answer !== "string" || !answer.trim()) {
return response.status(502).json({
error: "ساختار پاسخ سرویس قابل پردازش نبود.",
code: "INVALID_UPSTREAM_RESPONSE",
});
}
return response.json({
answer: answer.trim(),
});
} catch (error) {
if (!axios.isAxiosError(error)) {
console.error("Unexpected server error:", error);
return response.status(500).json({
error: "خطای پیشبینینشدهای رخ داد.",
code: "UNEXPECTED_ERROR",
});
}
if (error.code === "ECONNABORTED") {
return response.status(504).json({
error: "زمان انتظار دریافت پاسخ به پایان رسید.",
code: "UPSTREAM_TIMEOUT",
});
}
if (error.response?.status === 429) {
return response.status(429).json({
error: "تعداد درخواستها زیاد است؛ کمی بعد دوباره تلاش کنید.",
code: "RATE_LIMITED",
});
}
return response.status(502).json({
error: "سرویس هوش مصنوعی پاسخ معتبری برنگرداند.",
code: "UPSTREAM_ERROR",
});
}
});
app.listen(port, () => {
console.log(`Backend is running on http://localhost:${port}`);
});
اجرای پروژه
در Terminal اول:
cd server
npm run dev
در Terminal دوم و از ریشه Frontend:
npm run dev
بررسی سلامت Backend:
curl http://localhost:3000/api/health
آزمایش Chat:
curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"message":"Axios چیست و چه تفاوتی با Fetch دارد؟"}'
چرا API درواره را مستقیماً از React فراخوانی نکردیم؟
این روش ناامن است:
const response = await axios.post(
"https://api.darvareh.ir/v1/chat/completions",
requestBody,
{
headers: {
Authorization: "Bearer YOUR_DARVAREH_API_KEY",
},
}
);
کدی که در React نوشته میشود به مرورگر کاربر ارسال خواهد شد. کاربر میتواند از طریق Developer Tools، Source Map، Network Panel یا فایلهای Bundle به اطلاعات موجود در Frontend دسترسی پیدا کند.
قرار دادن کلید در فایل زیر نیز آن را امن نمیکند:
VITE_DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
متغیرهای دارای پیشوند VITE_ برای استفاده در Client طراحی شدهاند و نباید اطلاعات محرمانه داشته باشند.
مسیر درست:
React -> Backend شما -> API درواره
ارسال چند Request همزمان
برای درخواستهای مستقل از Promise.all استفاده کنید:
const [profileResponse, modelsResponse] = await Promise.all([
api.get("/profile"),
api.get("/models"),
]);
const profile = profileResponse.data;
const models = modelsResponse.data;
اگر یکی از Promiseها Reject شود، Promise.all نیز Reject میشود.
اگر میخواهید نتیجه همه Requestها را حتی در صورت شکست بعضی از آنها ببینید:
const results = await Promise.allSettled([
api.get("/profile"),
api.get("/models"),
]);
متد قدیمی axios.all در مستندات جدید به نفع Promise.all منسوخ اعلام شده است.
ارسال فایل با Axios
برای ارسال فایل در مرورگر:
const formData = new FormData();
formData.append("file", selectedFile);
formData.append("title", "فایل نمونه");
const response = await api.post("/uploads", formData);
در مرورگر معمولاً بهتر است اجازه دهید Axios و Browser مقدار Content-Type و Boundary مربوط به Multipart را تنظیم کنند.
const response = await api.post("/uploads", formData);
تنظیم دستی ناقص Boundary میتواند باعث شکست Request شود.
نمایش Progress آپلود
در محیطهای پشتیبانیشده:
const response = await api.post("/uploads", formData, {
onUploadProgress(progressEvent) {
if (!progressEvent.total) {
return;
}
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`${percent}%`);
},
});
میزان پشتیبانی و رفتار Progress میتواند به Environment و Adapter مورداستفاده بستگی داشته باشد.
اعتبارسنجی Status Code با validateStatus
Axios بهصورت پیشفرض Status Codeهای خارج از محدوده موفقیت را خطا در نظر میگیرد. با validateStatus میتوانید این رفتار را تغییر دهید:
const response = await api.get("/resource", {
validateStatus(status) {
return status >= 200 && status < 500;
},
});
در این مثال پاسخ 404 وارد then میشود، نه catch.
از تغییر گسترده این رفتار بدون دلیل مشخص خودداری کنید؛ زیرا ممکن است پاسخ ناموفق بهاشتباه موفق در نظر گرفته شود.
یک سناریوی منطقی:
const response = await api.get("/username/check", {
params: {
username: "example",
},
validateStatus(status) {
return status === 200 || status === 404;
},
});
const exists = response.status === 200;
Retry کردن Requestها
Retry میتواند برای خطاهای موقتی شبکه مفید باشد، اما نباید همه Requestها را بدون بررسی دوباره ارسال کنید.
معمولاً Retry برای Requestهای Read-only مانند GET کمریسکتر است. تکرار خودکار POST ممکن است باعث ایجاد عملیات تکراری، مصرف دوباره اعتبار یا ثبت چندباره اطلاعات شود.
یک Retry ساده برای GET:
async function getWithRetry(url, options = {}) {
const maxAttempts = 3;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
return await api.get(url, options);
} catch (error) {
const retryable =
!error.response ||
error.response.status === 429 ||
error.response.status >= 500;
if (!retryable || attempt === maxAttempts) {
throw error;
}
const delay = 500 * 2 ** (attempt - 1);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
در محیط Production بهتر است موارد زیر نیز بررسی شوند:
- Header مربوط به
Retry-After - اضافه کردن Jitter
- Idempotency عملیات
- حداکثر زمان کل
- لغو Request توسط کاربر
- بودجه و هزینه هر درخواست
- سیاست رسمی API مقصد
برای Requestهای تولید محتوای هوش مصنوعی، Retry خودکار باید با احتیاط بیشتری انجام شود؛ زیرا هر تلاش ممکن است مصرف و هزینه جداگانه ایجاد کند.
ساخت API Client قابل نگهداری
بهتر است Requestهای Axios مستقیماً در تمام کامپوننتها پراکنده نباشند.
ساختار پیشنهادی:
src/
├── components/
├── features/
│ └── chat/
│ ├── ChatForm.jsx
│ └── chatService.js
├── lib/
│ ├── api.js
│ └── errors.js
├── services/
│ ├── userService.js
│ └── modelService.js
└── App.jsx
کامپوننت باید مسئول نمایش رابط کاربری باشد و Service مسئول ارتباط با API:
export async function getModels() {
const response = await api.get("/models");
return response.data;
}
در کامپوننت:
const models = await getModels();
این تفکیک باعث میشود:
- تغییر URLها سادهتر باشد.
- مدیریت خطا متمرکز شود.
- تستنویسی آسانتر شود.
- کامپوننتها خواناتر بمانند.
- مهاجرت احتمالی از Axios به Fetch سادهتر شود.
استفاده از Axios با TypeScript
مدل Response را تعریف کنید:
interface ChatResponse {
answer: string;
}
interface ChatRequest {
message: string;
}
Request:
import axios from "axios";
const response = await axios.post<ChatResponse>(
"/api/chat",
{
message: "سلام",
} satisfies ChatRequest
);
console.log(response.data.answer);
برای Service:
export async function sendMessage(
message: string,
signal?: AbortSignal
): Promise<string> {
const response = await api.post<ChatResponse>(
"/chat",
{
message,
},
{
signal,
}
);
return response.data.answer;
}
TypeScript فقط ساختار موردانتظار زمان توسعه را بررسی میکند. داده دریافتی از شبکه همچنان باید در نقاط مهم Runtime اعتبارسنجی شود.
تست Axios Client
برای تست Unit میتوانید Instance یا Service را Mock کنید. بهتر است کامپوننت مستقیماً Axios را فراخوانی نکند تا تست آن به جزئیات شبکه وابسته نشود.
نمونه تابع مستقل:
export function createChatService(httpClient) {
return {
async send(message) {
const response = await httpClient.post("/chat", {
message,
});
return response.data.answer;
},
};
}
در برنامه:
const chatService = createChatService(api);
در تست:
const fakeClient = {
post: async () => ({
data: {
answer: "پاسخ آزمایشی",
},
}),
};
const service = createChatService(fakeClient);
const answer = await service.send("سلام");
console.log(answer);
این الگو وابستگی HTTP را قابلجایگزینی میکند.
اشتباهات رایج هنگام استفاده از Axios
استفاده از response بهجای response.data
اشتباه:
const users = await axios.get("/api/users");
console.log(users[0]);
درست:
const response = await axios.get("/api/users");
console.log(response.data[0]);
فراموش کردن await
اشتباه:
const response = axios.get("/api/users");
console.log(response.data);
درست:
const response = await axios.get("/api/users");
console.log(response.data);
قرار دادن API Key در Frontend
هیچ Secret خصوصی را در کد React، فایلهای VITE_ یا Bundle مرورگر قرار ندهید.
نبود Timeout
برای APIهای خارجی Timeout منطقی تعریف کنید:
const api = axios.create({
timeout: 15000,
});
Catch کردن خطا بدون ادامه دادن آن
این کد خطا را پنهان میکند:
api.interceptors.response.use(
(response) => response,
(error) => {
console.error(error);
}
);
نسخه صحیح:
api.interceptors.response.use(
(response) => response,
(error) => {
console.error(error);
return Promise.reject(error);
}
);
نمایش خطای خام سرور
این کار میتواند اطلاعات فنی غیرضروری یا حساس را به کاربر نشان دهد:
setError(JSON.stringify(error.response.data));
بهتر است Backend یک پیام کنترلشده برگرداند و Frontend آن را به زبان قابلفهم نمایش دهد.
Retry خودکار همه POSTها
درخواست POST را فقط زمانی Retry کنید که رفتار API، Idempotency و پیامدهای تکرار عملیات مشخص باشد.
ثبت اطلاعات حساس در Log
این موارد را در Console یا Log عمومی ثبت نکنید:
- API Key
- Authorization Header
- رمز عبور
- محتوای خصوصی کاربران
- Cookie
- Refresh Token
- اطلاعات کامل Requestهای حساس
چکلیست Axios برای Production
پیش از انتشار پروژه بررسی کنید:
- یک Axios Instance مرکزی ساخته شده است.
baseURLبرای هر محیط درست است.- Timeout منطقی تعریف شده است.
- خطاهای شبکه، Timeout و HTTP تفکیک میشوند.
- API Key در Backend نگهداری میشود.
- Interceptorها چندباره ثبت نمیشوند.
- Requestهای قابللغو از
AbortControllerاستفاده میکنند. - پیام خطا برای کاربر قابلفهم است.
- جزئیات داخلی سرور به Client ارسال نمیشود.
- Requestهای تکراری کنترل میشوند.
- Retry فقط برای خطاها و عملیات مناسب فعال است.
- Loading State در رابط کاربری وجود دارد.
- دکمه ارسال هنگام Request غیرفعال میشود.
- ورودی در Frontend و Backend اعتبارسنجی میشود.
- اندازه Request محدود شده است.
- اطلاعات حساس وارد Log نمیشوند.
- رفتار
401،403،404،429و5xxمشخص است. - ارتباط Production با HTTPS انجام میشود.
- هزینه و مصرف API هوش مصنوعی پایش میشود.
پرسشهای متداول
آیا Axios رایگان است؟
Axios یک کتابخانه متنباز است. بااینحال سرویسهای API، میزبانی و زیرساختی که با آن فراخوانی میکنید ممکن است هزینه داشته باشند.
آیا Axios فقط برای React است؟
خیر. Axios را میتوان در JavaScript خالص، Vue، Angular، React، Node.js و محیطهای سازگار دیگر استفاده کرد.
آیا Axios به jQuery نیاز دارد؟
خیر. Axios هیچ وابستگیای به jQuery ندارد.
Axios بهتر است یا Fetch؟
هیچ پاسخ مطلقی وجود ندارد. Fetch برای پروژههای ساده و بدون Dependency اضافه مناسب است. Axios برای پروژههایی که به Instance، Interceptor، Timeout و مدیریت متمرکز Requestها نیاز دارند میتواند راحتتر باشد.
چرا Axios برای خطای 404 وارد catch میشود؟
Axios بهصورت پیشفرض بسیاری از Status Codeهای خارج از محدوده موفقیت را Reject میکند. رفتار Fetch در این زمینه متفاوت است و باید response.ok را بهصورت دستی بررسی کنید.
چگونه Query Parameter ارسال کنیم؟
axios.get("/api/search", {
params: {
q: "هوش مصنوعی",
},
});
چگونه Body ارسال کنیم؟
axios.post("/api/chat", {
message: "سلام",
});
چگونه درخواست را لغو کنیم؟
const controller = new AbortController();
axios.get("/api/data", {
signal: controller.signal,
});
controller.abort();
آیا CancelToken هنوز توصیه میشود؟
خیر. CancelToken منسوخ شده و برای پروژههای جدید بهتر است از AbortController استفاده شود.
آیا میتوان API درواره را با Axios فراخوانی کرد؟
بله. در Backend میتوانید یک Axios Instance با Base URL زیر بسازید:
https://api.darvareh.ir/v1
Endpoint مربوط به Chat Completions:
https://api.darvareh.ir/v1/chat/completions
API Key باید در Backend و متغیر محیطی خصوصی نگهداری شود.
شناسه مدل را از کجا دریافت کنیم؟
مدلها، شناسههای قابلاستفاده و قیمت بهروز آنها در صفحه مدلهای درواره قابلمشاهدهاند.
جمعبندی
Axios یک HTTP Client کاربردی برای JavaScript، React و Node.js است. این کتابخانه ارسال Request، مدیریت Response، تعیین Timeout، لغو درخواست، ساخت Instance و استفاده از Interceptor را ساده میکند.
مهمترین نکات این آموزش:
- داده پاسخ Axios معمولاً در
response.dataقرار دارد. - Query Parameter با
paramsارسال میشود. - Body درخواست با
dataیا آرگومان دومpostارسال میشود. - Axios و Fetch رفتار متفاوتی در برابر Status Codeهای ناموفق دارند.
- برای پروژههای بزرگ بهتر است Axios Instance مرکزی بسازید.
- Interceptor برای منطق مشترک Request و Response مناسب است.
- لغو Requestهای جدید باید با
AbortControllerانجام شود. - Retry درخواستهای
POSTباید با احتیاط انجام شود. - API Key هوش مصنوعی نباید در React یا JavaScript مرورگر قرار بگیرد.
- Frontend باید از طریق Backend امن به API هوش مصنوعی متصل شود.
برای ساخت ابزارهای تولید محتوا، دستیارهای هوش مصنوعی، اپلیکیشنهای سازمانی و محصولات مبتنی بر مدلهای زبانی میتوانید Axios را در لایه ارتباطی پروژه و API هوش مصنوعی درواره را در Backend برنامه استفاده کنید.
مقالات مرتبط
- HTTP چیست؟ آموزش Request، Response، Method و Status Code
- REST API چیست؟ آموزش کامل طراحی و استفاده از RESTful API
- آموزش کامل JSON در Python، JavaScript و API هوش مصنوعی
- آموزش Node.js و Express برای اتصال به API هوش مصنوعی درواره
- آموزش اتصال به API درواره با cURL
- آموزش تست API درواره با Postman
- ساخت چتبات هوش مصنوعی با Next.js، React و API درواره
- چگونه یک API هوش مصنوعی آماده Production بسازیم؟
- آموزش اتصال API هوش مصنوعی به اپلیکیشن
منابع تکمیلی
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.