ارتباط با سرور و API
API و HTTP، Fetch API با Request و Response و Headers، متدهای HTTP، Query Parameters و Request Body، FormData و Blob و File، آپلود و دانلود، REST، CORS و AbortController.
API چیست؟
API (Application Programming Interface) قراردادی برای ارتباط برنامههاست. در وب معمولاً REST API با HTTP:
// کلاینت (مرورگر) ←→ API ←→ دیتابیس
// ساختار درخواست:
{
method: "GET", // متد
url: "/api/users", // آدرس
headers: { }, // سرصفحهها
body: { } // داده ارسالی
}
HTTP برای جاوا اسکریپت
| متد | کار | Idempotent |
|---|---|---|
GET | دریافت داده | بله |
POST | ایجاد | خیر |
PUT | جایگزینی کامل | بله |
PATCH | تغییر جزئی | خیر |
DELETE | حذف | بله |
کدهای وضعیت: 2xx موفق، 4xx خطای کلاینت، 5xx خطای سرور
Fetch API — دریافت داده
// سادهترین GET:
const response = await fetch("/api/users");
// ⚠️ fetch برای 404 خطا نمیدهد!
console.log(response.ok); // false برای 404
console.log(response.status); // 404
// باید دستی چک کنیم:
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
console.log(data);
Request کامل — POST با بدنه
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
},
body: JSON.stringify({
name: "علی",
email: "ali@example.com"
})
});
const result = await response.json();
پاسخ — Response Object
const res = await fetch("/api/data");
console.log(res.status); // 200
console.log(res.statusText); // "OK"
console.log(res.ok); // true
console.log(res.headers.get("Content-Type"));
console.log(res.url); // آدرس نهایی (بعد redirect)
// انواع خواندن بدنه:
const json = await res.json(); // JSON
const text = await res.text(); // متن
const blob = await res.blob(); // فایل باینری
const buffer = await res.arrayBuffer(); // بایتها
// فقط یکی قابل استفاده است!
Headers — سرصفحهها
// با شیء Headers:
const headers = new Headers({
"Content-Type": "application/json",
"X-API-Key": "abc123"
});
headers.append("X-Custom", "value");
headers.set("Content-Type", "text/plain");
console.log(headers.get("Content-Type"));
headers.delete("X-Custom");
// در fetch:
fetch(url, { headers });
Query Parameters
// با URLSearchParams:
const params = new URLSearchParams({
page: "1",
limit: "10",
sort: "name"
});
const res = await fetch(`/api/users?${params}`);
// داینامیک:
function buildUrl(base, params) {
const url = new URL(base);
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null) {
url.searchParams.set(k, v);
}
});
return url.toString();
}
console.log(buildUrl("/api/search", { q: "js", page: 2 }));
// /api/search?q=js&page=2
FormData — فرم و فایل
// از فرم موجود:
const form = document.querySelector("#upload");
const formData = new FormData(form);
// دستی:
const fd = new FormData();
fd.append("name", "علی");
fd.append("avatar", fileInput.files[0]); // فایل!
fd.append("docs", file2, "report.pdf"); // با نام دلخواه
// ارسال — Content-Type خودکار set میشود:
const res = await fetch("/api/upload", {
method: "POST",
body: formData
});
// پیمایش:
for (const [key, value] of fd.entries()) {
console.log(key, value);
}
Blob و File
// Blob — داده باینری خام:
const blob = new Blob(["متن"], { type: "text/plain" });
// File — Blob با نام و تاریخ:
const file = new File(["محتوا"], "test.txt", {
type: "text/plain",
lastModified: Date.now()
});
console.log(file.name);
console.log(file.size);
console.log(file.type);
// تبدیل:
const text = await file.text(); // خواندن متن
const buffer = await file.arrayBuffer();
const url = URL.createObjectURL(blob); // لینک موقت
آپلود فایل با پیشرفت
// fetch پیشرفت آپلود ندارد — XMLHttpRequest دارد:
function uploadWithProgress(file, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/upload");
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
onProgress(percent);
}
};
xhr.onload = () => resolve(JSON.parse(xhr.response));
xhr.onerror = () => reject(new Error("خطای شبکه"));
const fd = new FormData();
fd.append("file", file);
xhr.send(fd);
});
}
// استفاده:
uploadWithProgress(file, percent => {
console.log(`آپلود: ${percent.toFixed(0)}%`);
}).then(result => console.log("تمام!", result));
دانلود فایل
// دریافت باینری و دانلود:
async function downloadFile(url, filename) {
const res = await fetch(url);
const blob = await res.blob();
// ساخت لینک دانلود:
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
// پاکسازی:
URL.revokeObjectURL(link.href);
}
downloadFile("/api/report.pdf", "گزارش.pdf");
REST API — الگوهای استاندارد
// CRUD با REST:
class ApiClient {
#baseUrl;
constructor(baseUrl) {
this.#baseUrl = baseUrl;
}
// GET همه:
async list(resource, params = {}) {
const qs = new URLSearchParams(params);
const res = await fetch(`${this.#baseUrl}/${resource}?${qs}`);
return this.#handle(res);
}
// GET یکی:
async get(resource, id) {
return this.#handle(
await fetch(`${this.#baseUrl}/${resource}/${id}`)
);
}
// POST ایجاد:
async create(resource, data) {
return this.#handle(await fetch(`${this.#baseUrl}/${resource}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
}));
}
// PUT بهروزرسانی:
async update(resource, id, data) {
return this.#handle(await fetch(`${this.#baseUrl}/${resource}/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
}));
}
// DELETE:
async remove(resource, id) {
return this.#handle(await fetch(`${this.#baseUrl}/${resource}/${id}`, {
method: "DELETE"
}));
}
async #handle(res) {
if (!res.ok) {
const error = await res.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${res.status}`);
}
return res.json();
}
}
const api = new ApiClient("https://api.example.com");
await api.create("users", { name: "علی" });
await api.list("users", { page: 1 });
احراز هویت API
// Bearer Token — رایجترین:
fetch("/api/profile", {
headers: {
"Authorization": `Bearer ${accessToken}`
}
});
// API Key:
fetch("/api/data", {
headers: { "X-API-Key": "your-key" }
});
// توکن منقضی — Refresh:
async function authFetch(url, options = {}) {
let token = localStorage.getItem("token");
const res = await fetch(url, {
...options,
headers: {
...options.headers,
"Authorization": `Bearer ${token}`
}
});
// 401 — refresh:
if (res.status === 401) {
const refreshRes = await fetch("/api/refresh", {
method: "POST",
body: localStorage.getItem("refreshToken")
});
const { token: newToken } = await refreshRes.json();
localStorage.setItem("token", newToken);
// تلاش مجدد:
return authFetch(url, options);
}
return res;
}
AbortController — لغو درخواست
const controller = new AbortController();
fetch("/api/search?q=" + query, { signal: controller.signal })
.then(r => r.json())
.then(data => console.log(data))
.catch(e => {
if (e.name === "AbortError") {
console.log("لغو شد!");
}
});
// لغو:
controller.abort();
// کاربرد — جستجوی زنده:
let controller;
searchInput.addEventListener("input", async (e) => {
controller?.abort(); // قبلی را لغو کن
controller = new AbortController();
const res = await fetch(`/api/search?q=${e.target.value}`, {
signal: controller.signal
});
const results = await res.json();
renderResults(results);
});
CORS — اشتراک منابع
مرورگر درخواست بیندامنهای را محدود میکند — سرور باید اجازه دهد:
// از site-a.com به api.site-b.com — CORS!
// سرور باید هدر بدهد:
// Access-Control-Allow-Origin: https://site-a.com
// درخواستهای ساده مستقیم میروند.
// درخواستهای پیچیده اول OPTIONS (preflight) میفرستند.
// خطای CORS در کلاینت قابل حل نیست —
// سرور باید هدرهای مناسب بفرستد!
XMLHttpRequest — روش قدیمی
// قبل از fetch — هنوز برای آپلود با پیشرفت:
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data");
xhr.onload = () => {
if (xhr.status === 200) {
console.log(JSON.parse(xhr.response));
}
};
xhr.onerror = () => console.log("خطا");
xhr.send();
جمعبندی
| کار | ابزار |
|---|---|
| GET/POST ساده | fetch |
| JSON | JSON.stringify/parse |
| فایل/فرم | FormData |
| پیشرفت آپلود | XMLHttpRequest |
| لغو | AbortController |
| query | URLSearchParams |
این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.