เชี่ยวชาญการเรียกใช้ API แบบ Type-Safe ใน TypeScript เพื่อสร้างเว็บแอปพลิเคชันที่แข็งแกร่ง บำรุงรักษาง่าย และปราศจากข้อผิดพลาด เรียนรู้แนวทางปฏิบัติที่ดีที่สุดและเทคนิคขั้นสูง
คู่มือฉบับสมบูรณ์: การเรียกใช้ API แบบ Type-Safe ด้วย TypeScript
ในการพัฒนาเว็บสมัยใหม่ การโต้ตอบกับ API ถือเป็นงานพื้นฐาน TypeScript ซึ่งมีระบบไทป์ที่ทรงพลัง มอบข้อได้เปรียบที่สำคัญในการรับประกันความน่าเชื่อถือและความสามารถในการบำรุงรักษาแอปพลิเคชันของคุณ โดยเปิดใช้งานการเรียกใช้ API แบบ Type-Safe คู่มือนี้จะสำรวจวิธีใช้ประโยชน์จากฟีเจอร์ของ TypeScript เพื่อสร้างการโต้ตอบกับ API ที่แข็งแกร่งและปราศจากข้อผิดพลาด ครอบคลุมถึงแนวทางปฏิบัติที่ดีที่สุด เทคนิคขั้นสูง และตัวอย่างจากโลกแห่งความเป็นจริง
เหตุใด Type Safety จึงสำคัญสำหรับการเรียกใช้ API
เมื่อทำงานกับ API โดยพื้นฐานแล้วคุณกำลังจัดการกับข้อมูลที่มาจากแหล่งภายนอก ข้อมูลนี้อาจไม่ได้อยู่ในรูปแบบที่คุณคาดหวังเสมอไป ซึ่งนำไปสู่ข้อผิดพลาดขณะรันไทม์และพฤติกรรมที่ไม่คาดคิด Type safety เป็นการป้องกันที่สำคัญโดยการตรวจสอบว่าข้อมูลที่คุณได้รับนั้นสอดคล้องกับโครงสร้างที่กำหนดไว้ล่วงหน้า ซึ่งช่วยจับประเด็นที่อาจเกิดขึ้นได้ตั้งแต่เนิ่นๆ ในกระบวนการพัฒนา
- ลดข้อผิดพลาดขณะรันไทม์: การตรวจสอบไทป์ ณ เวลาคอมไพล์ช่วยระบุและแก้ไขข้อผิดพลาดที่เกี่ยวกับไทป์ก่อนที่จะไปถึงเวอร์ชันใช้งานจริง
- ปรับปรุงความสามารถในการบำรุงรักษาโค้ด: การกำหนดไทป์ที่ชัดเจนทำให้โค้ดของคุณเข้าใจและแก้ไขได้ง่ายขึ้น ลดความเสี่ยงในการเกิดบั๊กใหม่ระหว่างการปรับปรุงโค้ด (refactoring)
- เพิ่มความสามารถในการอ่านโค้ด: คำอธิบายไทป์ (Type annotations) เป็นเอกสารประกอบที่มีคุณค่า ทำให้นักพัฒนาเข้าใจโครงสร้างข้อมูลที่คาดหวังได้ง่ายขึ้น
- ประสบการณ์นักพัฒนาที่ดีขึ้น: การสนับสนุนของ IDE สำหรับการตรวจสอบไทป์และการเติมโค้ดอัตโนมัติ (autocompletion) ช่วยปรับปรุงประสบการณ์ของนักพัฒนาและลดโอกาสเกิดข้อผิดพลาดได้อย่างมาก
การตั้งค่าโปรเจกต์ TypeScript ของคุณ
ก่อนที่จะเริ่มเรียกใช้ API ตรวจสอบให้แน่ใจว่าคุณได้ตั้งค่าโปรเจกต์ TypeScript เรียบร้อยแล้ว หากคุณเริ่มต้นจากศูนย์ คุณสามารถเริ่มต้นโปรเจกต์ใหม่ได้โดยใช้:
npm init -y
npm install typescript --save-dev
tsc --init
คำสั่งนี้จะสร้างไฟล์ `tsconfig.json` พร้อมตัวเลือกคอมไพเลอร์ TypeScript เริ่มต้น คุณสามารถปรับแต่งตัวเลือกเหล่านี้ให้เหมาะกับความต้องการของโปรเจกต์ของคุณได้ ตัวอย่างเช่น คุณอาจต้องการเปิดใช้งาน strict mode เพื่อการตรวจสอบไทป์ที่เข้มงวดยิ่งขึ้น:
// tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
การกำหนดไทป์สำหรับ Response ของ API
ขั้นตอนแรกในการทำให้การเรียกใช้ API เป็นแบบ Type-Safe คือการกำหนดไทป์ของ TypeScript ที่แสดงถึงโครงสร้างของข้อมูลที่คุณคาดว่าจะได้รับจาก API ซึ่งโดยปกติจะทำโดยใช้การประกาศ `interface` หรือ `type`
การใช้ Interfaces
Interfaces เป็นวิธีที่ทรงพลังในการกำหนดรูปทรงของอ็อบเจกต์ ตัวอย่างเช่น หากคุณกำลังดึงรายชื่อผู้ใช้จาก API คุณอาจกำหนด interface ดังนี้:
interface User {
id: number;
name: string;
email: string;
address?: string; // property ที่เป็นทางเลือก (optional)
phone?: string; // property ที่เป็นทางเลือก (optional)
website?: string; // property ที่เป็นทางเลือก (optional)
company?: {
name: string;
catchPhrase: string;
bs: string;
};
}
เครื่องหมาย `?` หลังชื่อ property บ่งชี้ว่า property นั้นเป็นทางเลือก (optional) ซึ่งมีประโยชน์สำหรับการจัดการกับ response ของ API ที่บางฟิลด์อาจหายไป
การใช้ Types
Types มีความคล้ายคลึงกับ interfaces แต่มีความยืดหยุ่นมากกว่า รวมถึงความสามารถในการกำหนด union types และ intersection types คุณสามารถได้ผลลัพธ์เช่นเดียวกับ interface ด้านบนโดยใช้ type:
type User = {
id: number;
name: string;
email: string;
address?: string; // property ที่เป็นทางเลือก (optional)
phone?: string; // property ที่เป็นทางเลือก (optional)
website?: string; // property ที่เป็นทางเลือก (optional)
company?: {
name: string;
catchPhrase: string;
bs: string;
};
};
สำหรับโครงสร้างอ็อบเจกต์ที่ไม่ซับซ้อน interfaces และ types มักจะใช้แทนกันได้ อย่างไรก็ตาม types จะมีประสิทธิภาพมากขึ้นเมื่อต้องจัดการกับสถานการณ์ที่ซับซ้อนกว่า
การเรียกใช้ API ด้วย Axios
Axios เป็น HTTP client ที่ได้รับความนิยมสำหรับการสร้างคำขอ API ใน JavaScript และ TypeScript มันมี API ที่สะอาดและใช้งานง่าย ทำให้ง่ายต่อการจัดการกับ HTTP methods, request headers และ response data ที่แตกต่างกัน
การติดตั้ง Axios
npm install axios
การเรียกใช้ API แบบมีไทป์
ในการเรียกใช้ API แบบ type-safe ด้วย Axios คุณสามารถใช้เมธอด `axios.get` และระบุไทป์ของ response ที่คาดหวังโดยใช้ generics:
import axios from 'axios';
async function fetchUsers(): Promise {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
return response.data;
} catch (error) {
console.error('เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้:', error);
throw error;
}
}
fetchUsers().then(users => {
users.forEach(user => {
console.log(user.name);
});
});
ในตัวอย่างนี้ `axios.get
การจัดการ HTTP Methods ที่แตกต่างกัน
Axios รองรับ HTTP methods ที่หลากหลาย รวมถึง `GET`, `POST`, `PUT`, `DELETE`, และ `PATCH` คุณสามารถใช้เมธอดที่สอดคล้องกันเพื่อสร้างคำขอ API ประเภทต่างๆ ได้ ตัวอย่างเช่น ในการสร้างผู้ใช้ใหม่ คุณอาจใช้เมธอด `axios.post`:
async function createUser(user: Omit): Promise {
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/users', user);
return response.data;
} catch (error) {
console.error('เกิดข้อผิดพลาดในการสร้างผู้ใช้:', error);
throw error;
}
}
const newUser = {
name: 'John Doe',
email: 'john.doe@example.com',
address: '123 Main St',
phone: '555-1234',
website: 'example.com',
company: {
name: 'Example Corp',
catchPhrase: 'Leading the way',
bs: 'Innovative solutions'
}
};
createUser(newUser).then(user => {
console.log('ผู้ใช้ที่สร้างขึ้น:', user);
});
ในตัวอย่างนี้ `Omit
การใช้ Fetch API
Fetch API เป็น API ที่มีอยู่ใน JavaScript สำหรับการสร้างคำขอ HTTP แม้ว่ามันจะพื้นฐานกว่า Axios แต่ก็สามารถใช้กับ TypeScript เพื่อให้ได้การเรียกใช้ API แบบ type-safe ได้เช่นกัน คุณอาจต้องการใช้มันเพื่อหลีกเลี่ยงการเพิ่ม dependency หากมันตอบโจทย์ความต้องการของคุณ
การเรียกใช้ API แบบมีไทป์ด้วย Fetch
ในการเรียกใช้ API แบบ type-safe ด้วย Fetch คุณสามารถใช้ฟังก์ชัน `fetch` แล้วแยกวิเคราะห์ response เป็น JSON โดยระบุไทป์ของ response ที่คาดหวัง:
async function fetchUsers(): Promise {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: User[] = await response.json();
return data;
} catch (error) {
console.error('เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้:', error);
throw error;
}
}
fetchUsers().then(users => {
users.forEach(user => {
console.log(user.name);
});
});
ในตัวอย่างนี้ `const data: User[] = await response.json();` บอก TypeScript ว่าข้อมูล response ควรถือเป็นอาร์เรย์ของอ็อบเจกต์ `User` ซึ่งช่วยให้ TypeScript สามารถทำการตรวจสอบไทป์และการเติมโค้ดอัตโนมัติได้
การจัดการ HTTP Methods ที่แตกต่างกันด้วย Fetch
ในการสร้างคำขอ API ประเภทต่างๆ ด้วย Fetch คุณสามารถใช้ฟังก์ชัน `fetch` พร้อมกับตัวเลือกต่างๆ เช่น `method` และ `body` ตัวอย่างเช่น ในการสร้างผู้ใช้ใหม่ คุณอาจใช้โค้ดต่อไปนี้:
async function createUser(user: Omit): Promise {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: User = await response.json();
return data;
} catch (error) {
console.error('เกิดข้อผิดพลาดในการสร้างผู้ใช้:', error);
throw error;
}
}
const newUser = {
name: 'John Doe',
email: 'john.doe@example.com',
address: '123 Main St',
phone: '555-1234',
website: 'example.com',
company: {
name: 'Example Corp',
catchPhrase: 'Leading the way',
bs: 'Innovative solutions'
}
};
createUser(newUser).then(user => {
console.log('ผู้ใช้ที่สร้างขึ้น:', user);
});
การจัดการข้อผิดพลาดของ API
การจัดการข้อผิดพลาดเป็นสิ่งสำคัญอย่างยิ่งในการเรียกใช้ API, API อาจล้มเหลวได้จากหลายสาเหตุ เช่น ปัญหาการเชื่อมต่อเครือข่าย ข้อผิดพลาดของเซิร์ฟเวอร์ และคำขอที่ไม่ถูกต้อง สิ่งสำคัญคือต้องจัดการกับข้อผิดพลาดเหล่านี้อย่างเหมาะสมเพื่อป้องกันไม่ให้แอปพลิเคชันของคุณขัดข้องหรือแสดงพฤติกรรมที่ไม่คาดคิด
การใช้ Try-Catch Blocks
วิธีที่พบบ่อยที่สุดในการจัดการข้อผิดพลาดในโค้ดแบบอะซิงโครนัสคือการใช้ try-catch blocks ซึ่งช่วยให้คุณสามารถดักจับ exception ใดๆ ที่ถูกโยนออกมาในระหว่างการเรียกใช้ API และจัดการได้อย่างเหมาะสม
async function fetchUsers(): Promise {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
return response.data;
} catch (error) {
console.error('เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้:', error);
// จัดการข้อผิดพลาด เช่น แสดงข้อความข้อผิดพลาดแก่ผู้ใช้
throw error; // โยนข้อผิดพลาดต่อไปเพื่อให้โค้ดที่เรียกใช้สามารถจัดการได้เช่นกัน
}
}
การจัดการ Error Codes ที่เฉพาะเจาะจง
API มักจะส่งคืน error codes ที่เฉพาะเจาะจงเพื่อบ่งชี้ประเภทของข้อผิดพลาดที่เกิดขึ้น คุณสามารถใช้ error codes เหล่านี้เพื่อให้การจัดการข้อผิดพลาดมีความเฉพาะเจาะจงมากขึ้น ตัวอย่างเช่น คุณอาจต้องการแสดงข้อความข้อผิดพลาดที่แตกต่างกันสำหรับข้อผิดพลาด 404 Not Found กับข้อผิดพลาด 500 Internal Server Error
async function fetchUser(id: number): Promise {
try {
const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
return response.data;
} catch (error: any) {
if (error.response?.status === 404) {
console.log(`ไม่พบผู้ใช้ที่มี ID ${id}`);
return null; // หรือโยน custom error
} else {
console.error('เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้:', error);
throw error;
}
}
}
fetchUser(123).then(user => {
if (user) {
console.log('ผู้ใช้:', user);
} else {
console.log('ไม่พบผู้ใช้');
}
});
การสร้าง Custom Error Types
สำหรับสถานการณ์การจัดการข้อผิดพลาดที่ซับซ้อนยิ่งขึ้น คุณสามารถสร้าง custom error types เพื่อแสดงถึงข้อผิดพลาด API ประเภทต่างๆ ได้ ซึ่งช่วยให้คุณสามารถให้ข้อมูลข้อผิดพลาดที่มีโครงสร้างมากขึ้นและจัดการข้อผิดพลาดได้อย่างมีประสิทธิภาพมากขึ้น
class ApiError extends Error {
constructor(public statusCode: number, message: string) {
super(message);
this.name = 'ApiError';
}
}
async function fetchUser(id: number): Promise {
try {
const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
return response.data;
} catch (error: any) {
if (error.response?.status === 404) {
throw new ApiError(404, `ไม่พบผู้ใช้ที่มี ID ${id}`);
} else {
console.error('เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้:', error);
throw new ApiError(500, 'Internal Server Error'); // หรือ status code อื่นๆ ที่เหมาะสม
}
}
}
fetchUser(123).catch(error => {
if (error instanceof ApiError) {
console.error(`API Error: ${error.statusCode} - ${error.message}`);
} else {
console.error('เกิดข้อผิดพลาดที่ไม่คาดคิด:', error);
}
});
การตรวจสอบข้อมูล (Data Validation)
แม้จะมีระบบไทป์ของ TypeScript ก็ยังคงเป็นสิ่งสำคัญที่จะต้องตรวจสอบข้อมูลที่คุณได้รับจาก API ณ เวลาทำงาน (runtime) API สามารถเปลี่ยนแปลงโครงสร้างการตอบสนองได้โดยไม่ต้องแจ้งให้ทราบล่วงหน้า และไทป์ TypeScript ของคุณอาจไม่ซิงค์กับ response จริงของ API ได้อย่างสมบูรณ์แบบเสมอไป
การใช้ Zod สำหรับการตรวจสอบขณะรันไทม์
Zod เป็นไลบรารี TypeScript ยอดนิยมสำหรับการตรวจสอบข้อมูลขณะรันไทม์ มันช่วยให้คุณสามารถกำหนด schema ที่อธิบายโครงสร้างที่คาดหวังของข้อมูลของคุณ แล้วตรวจสอบข้อมูลกับ schema เหล่านั้น ณ เวลาทำงาน
การติดตั้ง Zod
npm install zod
การตรวจสอบ API Responses ด้วย Zod
ในการตรวจสอบ API responses ด้วย Zod คุณสามารถกำหนด Zod schema ที่สอดคล้องกับไทป์ TypeScript ของคุณ แล้วใช้เมธอด `parse` เพื่อตรวจสอบข้อมูล
import { z } from 'zod';
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
address: z.string().optional(),
phone: z.string().optional(),
website: z.string().optional(),
company: z.object({
name: z.string(),
catchPhrase: z.string(),
bs: z.string(),
}).optional(),
});
type User = z.infer;
async function fetchUsers(): Promise {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
const data = z.array(userSchema).parse(response.data);
return data;
} catch (error) {
console.error('เกิดข้อผิดพลาดในการดึงข้อมูลผู้ใช้:', error);
throw error;
}
}
ในตัวอย่างนี้ `z.array(userSchema).parse(response.data)` จะตรวจสอบว่าข้อมูล response เป็นอาร์เรย์ของอ็อบเจกต์ที่สอดคล้องกับ `userSchema` หากข้อมูลไม่เป็นไปตาม schema, Zod จะโยนข้อผิดพลาดซึ่งคุณสามารถจัดการได้อย่างเหมาะสม
เทคนิคขั้นสูง
การใช้ Generics สำหรับฟังก์ชัน API ที่นำกลับมาใช้ใหม่ได้
Generics ช่วยให้คุณสามารถเขียนฟังก์ชัน API ที่นำกลับมาใช้ใหม่ได้ซึ่งสามารถจัดการกับข้อมูลประเภทต่างๆ ได้ ตัวอย่างเช่น คุณสามารถสร้างฟังก์ชัน `fetchData` แบบ generic ที่สามารถดึงข้อมูลจาก API endpoint ใดก็ได้และส่งคืนข้อมูลพร้อมไทป์ที่ถูกต้อง
async function fetchData(url: string): Promise {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(`เกิดข้อผิดพลาดในการดึงข้อมูลจาก ${url}:`, error);
throw error;
}
}
// การใช้งาน
fetchData('https://jsonplaceholder.typicode.com/users').then(users => {
console.log('ผู้ใช้:', users);
});
fetchData<{ title: string; body: string }>('https://jsonplaceholder.typicode.com/todos/1').then(todo => {
console.log('Todo', todo)
});
การใช้ Interceptors สำหรับการจัดการข้อผิดพลาดแบบ Global
Axios มี interceptors ที่ช่วยให้คุณสามารถดักจับ request และ response ก่อนที่จะถูกจัดการโดยโค้ดของคุณ คุณสามารถใช้ interceptors เพื่อใช้การจัดการข้อผิดพลาดแบบ global เช่น การบันทึกข้อผิดพลาดหรือการแสดงข้อความข้อผิดพลาดแก่ผู้ใช้
axios.interceptors.response.use(
(response) => response,
(error) => {
console.error('ตัวจัดการข้อผิดพลาดแบบ Global:', error);
// แสดงข้อความข้อผิดพลาดแก่ผู้ใช้
return Promise.reject(error);
}
);
การใช้ Environment Variables สำหรับ API URLs
เพื่อหลีกเลี่ยงการ hardcode URL ของ API ในโค้ดของคุณ คุณสามารถใช้ environment variables เพื่อจัดเก็บ URL ได้ ซึ่งทำให้ง่ายต่อการกำหนดค่าแอปพลิเคชันของคุณสำหรับสภาพแวดล้อมต่างๆ เช่น development, staging และ production
ตัวอย่างการใช้ไฟล์ `.env` และแพ็คเกจ `dotenv`
// .env
API_URL=https://api.example.com
// ติดตั้ง dotenv
npm install dotenv
// นำเข้าและกำหนดค่า dotenv
import * as dotenv from 'dotenv'
dotenv.config()
const apiUrl = process.env.API_URL || 'http://localhost:3000'; // กำหนดค่าเริ่มต้น
async function fetchData(endpoint: string): Promise {
try {
const response = await axios.get(`${apiUrl}/${endpoint}`);
return response.data;
} catch (error) {
console.error(`เกิดข้อผิดพลาดในการดึงข้อมูลจาก ${apiUrl}/${endpoint}:`, error);
throw error;
}
}
สรุป
การเรียกใช้ API แบบ Type-safe เป็นสิ่งจำเป็นสำหรับการสร้างเว็บแอปพลิเคชันที่แข็งแกร่ง บำรุงรักษาง่าย และปราศจากข้อผิดพลาด TypeScript มีฟีเจอร์ที่ทรงพลังที่ช่วยให้คุณสามารถกำหนดไทป์สำหรับ API responses, ตรวจสอบข้อมูลขณะรันไทม์ และจัดการข้อผิดพลาดได้อย่างงดงาม ด้วยการปฏิบัติตามแนวทางปฏิบัติและเทคนิคที่ดีที่สุดที่ระบุไว้ในคู่มือนี้ คุณสามารถปรับปรุงคุณภาพและความน่าเชื่อถือของการโต้ตอบกับ API ของคุณได้อย่างมาก
ด้วยการใช้ TypeScript และไลบรารีอย่าง Axios และ Zod คุณสามารถมั่นใจได้ว่าการเรียกใช้ API ของคุณเป็นแบบ type-safe, ข้อมูลของคุณได้รับการตรวจสอบ และข้อผิดพลาดของคุณได้รับการจัดการอย่างเหมาะสม ซึ่งจะนำไปสู่แอปพลิเคชันที่แข็งแกร่งและบำรุงรักษาง่ายขึ้น
จำไว้เสมอว่าต้องตรวจสอบข้อมูลของคุณขณะรันไทม์ แม้ว่าจะมีระบบไทป์ของ TypeScript ก็ตาม API สามารถเปลี่ยนแปลงได้ และไทป์ของคุณอาจไม่ซิงค์กับ response จริงของ API ได้อย่างสมบูรณ์แบบเสมอไป การตรวจสอบข้อมูลของคุณขณะรันไทม์จะช่วยให้คุณสามารถจับประเด็นที่อาจเกิดขึ้นได้ก่อนที่จะก่อให้เกิดปัญหาในแอปพลิเคชันของคุณ
ขอให้สนุกกับการเขียนโค้ด!