چطور پاسخ API را در TypeScript تایپ کنیم
fetch داده را با تایپ any به شما میدهد، و این یعنی TypeScript دقیقاً همانجایی خاموش میشود که باگها از آن میآیند. در این راهنما میبینید چطور سریع و امن به دادههای API تایپ واقعی بدهید.
TypeScript کد شما را بررسی میکند، اما نمیتواند دادهای را که در زمان اجرا میرسد بررسی کند. بهمحض اینکه await response.json() را صدا میزنید، مقداری از نوع any میگیرید، و از آن به بعد کامپایلر اجازه میدهد user.adress.city را با غلط املایی بخوانید، بیآنکه اعتراضی کند. تایپ کردن پاسخهای API همین شکاف را میبندد. این راهنما نشان میدهد چطور تایپها را سریع از روی یک نمونه بنویسید، بخشهای دردسرساز را چطور مدل کنید، و کجا تایپ بهتنهایی کافی نیست.
مرحلهٔ ۱: تایپها را از یک نمونهٔ واقعی بسازید
نوشتن دستی interface برای یک پاسخ بزرگ کند است و پر از خطا. بهجای آن یک پاسخ واقعی را کپی کنید، مثلاً از تب Network مرورگر یا از مستندات API، و آن را در تبدیل JSON به TypeScript بچسبانید. پاسخی مثل این:
{
"id": 1024,
"name": "Ada",
"tags": ["admin", "beta"],
"address": { "city": "London", "zip": "N1 9GU" },
"orders": [
{ "order_id": "A-17", "total": 42, "shipped_at": "2026-09-01T10:00:00Z" },
{ "order_id": "A-18", "total": 18.5, "shipped_at": null, "coupon": "WELCOME" }
]
}
وقتی نام ریشه را روی User بگذارید، به این تبدیل میشود:
export interface Address {
city: string;
zip: string;
}
export interface Order {
order_id: string;
total: number;
shipped_at: string | null;
coupon?: string;
}
export interface User {
id: number;
name: string;
tags: string[];
address: Address;
orders: Order[];
}
ببینید مولد از روی دو سفارش چه چیزهایی را فهمیده است: shipped_at در یکی از آیتمها null بود، پس تایپش string | null شده؛ coupon در یکی از آیتمها وجود نداشت، پس اختیاری شده. شیءهای تودرتو هر کدام interface نامدار خودشان را گرفتهاند و آیتمهای orders به Order تبدیل شدهاند. اگر این کار را دستی انجام میدادید، باید همهٔ آیتمهای آرایه را یکییکی با هم مقایسه میکردید تا ببینید کدام کلید همیشه هست و کدام نه. همهٔ این کارها داخل مرورگر انجام میشود، پس پاسخی که دادههای واقعی مشتریها را دارد به جایی فرستاده نمیشود.
مرحلهٔ ۲: تایپ را با fetch به کار ببرید
سادهترین راه وصل کردن تایپ به داده، یک تابع کمکی کوچک است:
async function getJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return (await res.json()) as T;
}
const user = await getJson<User>('/api/users/1024');
user.address.city; // string, checked by the compiler
عبارت as T قولی است که شما به کامپایلر میدهید، نه یک بررسی. اگر سرور چیز دیگری بفرستد، TypeScript متوجه نمیشود. برای APIهای داخلی که کنترلشان دست خودتان است، این مصالحه اغلب قابل قبول است؛ برای هر چیز دیگری، ادامه را تا مرحلهٔ ۴ بخوانید. به زبان ساده، as T فقط برچسبی است که روی داده میزنید؛ کامپایلر از آن به بعد به این برچسب اعتماد میکند، اما خود داده در زمان اجرا همان چیزی است که سرور فرستاده، چه با برچسب جور باشد چه نباشد.
مرحلهٔ ۳: بخشهای دردسرساز را درست مدل کنید
| در JSON | در TypeScript | چرا |
|---|---|---|
| کلیدی که گاهی وجود ندارد | coupon?: string | اختیاری: ممکن است این ویژگی اصلاً وجود نداشته باشد |
| مقداری که گاهی null است | shipped_at: string | null | Nullable: ویژگی وجود دارد اما میتواند خالی باشد |
| هر دو حالت | note?: string | null | ممکن است API آن را حذف کند یا null بفرستد |
| تاریخ | string | JSON نوع دادهای برای تاریخ ندارد؛ هر جا لازم بود با new Date() تبدیلش کنید |
| شناسهٔ بزرگ | string، اگر API آن را داخل گیومه میفرستد | عددهای بزرگتر از 2^53 در جاوااسکریپت دقتشان را از دست میدهند |
| آرایهٔ خالی در نمونه | unknown[] | نمونه نشان نمیدهد آیتمها چه شکلی دارند |
برای هر چیزی که نمونه دربارهاش چیزی نمیگوید، unknown را به any ترجیح دهید: unknown شما را وادار میکند پیش از استفاده بررسی کنید، اما any بیسروصدا بررسی را خاموش میکند. تایپی که ساخته میشود فقط به اندازهٔ نمونهاش کامل است، پس از پاسخی استفاده کنید که فیلدهای اختیاری را هم دارد، یا آرایهای با چند آیتم متنوع.
مرحلهٔ ۴: وقتی داده مال شما نیست، در زمان اجرا اعتبارسنجی کنید
APIهای شخص ثالث تغییر میکنند، پراکسیها صفحهٔ خطای HTML برمیگردانند و کلاینتهای قدیمی با سرورهای جدید روبهرو میشوند. بررسی در زمان اجرا این موارد را درست در مرز ورود داده و با یک خطای روشن میگیرد، نه جایی در عمق رابط کاربری. دو روش رایج:
- JSON Schema: از همان نمونه با ساخت JSON Schema یک اسکیما بسازید و با کتابخانهای مثل Ajv اعتبارسنجی کنید. اسکیما به زبان برنامهنویسی خاصی وابسته نیست، پس بکاندی که به زبان دیگری نوشته شده هم میتواند از همان فایل استفاده کند.
- اعتبارسنج مخصوص TypeScript: کتابخانههایی مثل Zod اجازه میدهند ساختار داده را یک بار توصیف کنید و تایپ TypeScript را از همان توصیف استخراج کنید، تا تایپ و بررسی هیچوقت از هم فاصله نگیرند.
در هر دو حالت، یک بار و همان جایی که داده وارد برنامه میشود اعتبارسنجی کنید و بگذارید بقیهٔ کد به تایپ تکیه کند. به این ترتیب بررسی فقط در یک نقطه انجام میشود و بقیهٔ توابع لازم نیست هر بار دوباره شکل داده را وارسی کنند.
وقتی API تغییر میکند
تایپهایی که یک بار ساخته شدهاند، با رشد API کمکم از واقعیت فاصله میگیرند. نمونهای را که تایپها را از روی آن ساختهاید، کنار تایپها در مخزن کد نگه دارید تا نفر بعدی بتواند دوباره آنها را بسازد و تغییرات را در یک diff ببیند. اگر API توصیف OpenAPI منتشر میکند، ساختن تایپ از روی آن قابلاعتمادتر از ساختن از روی نمونه است، چون همهٔ فیلدهای اختیاری و همهٔ مقدارهای ممکن را فهرست میکند، از جمله آنهایی که هیچ پاسخ منفردی نشانشان نمیدهد. برای APIهایی که چنین توصیفی ندارند، اعتبارسنج زمان اجرا از مرحلهٔ ۴ یک ناهماهنگی بیصدا را به خطایی تبدیل میکند که در لاگها دیده میشود.
interface یا type؟
برای دادههای API هر دو کار میکنند. interfaceها را میشود گسترش داد و اگر دو بار تعریف شوند با هم ادغام میشوند، که برخی تیمها برای تایپهای عمومی میپسندند؛ type aliasها علاوه بر این میتوانند union و mapped type را هم بیان کنند. برای پروژه یک سبک انتخاب کنید و به آن پایبند بمانید. مولد هر دو را میسازد.
زبانهای دیگر از همان نمونه
همان نمونهٔ JSON را میشود در تبدیل JSON به تایپ به ساختارهای Go، مدلهای Pydantic یا کلاسهای C# هم تبدیل کرد، که کمک میکند فرانتاند و بکاند روی یک ساختار داده بمانند. اگر خود نمونه خراب است، فرمتکنندهٔ JSON نشان میدهد مشکل کجاست.