การจัดการ API Requests
Shared API Requests
เริ่มด้วยการวาง Logic การเรียก API ที่ใช้ร่วมกันไว้ใน shared/api สิ่งนี้ช่วยให้คุณ Reuse คำสั่ง Request ได้ง่ายทั่วทั้งแอป และช่วยให้ Prototyping ได้เร็วขึ้น สำหรับหลายๆ โปรเจกต์ แค่นี้ก็เพียงพอแล้วสำหรับการเรียก API
โครงสร้างไฟล์ทั่วไปจะเป็นดังนี้:
- 📂 shared
- 📂 api
- 📄 client.ts
- 📄 index.ts
- 📂 endpoints
- 📄 login.ts
- 📂 api
ไฟล์ client.ts รวมการตั้งค่า HTTP request ไว้ที่เดียว มันจะห่อหุ้ม Method ที่คุณเลือกใช้ (เช่น fetch() หรือ axios instance) และจัดการ Configuration ทั่วไป เช่น:
- Backend base URL
- Default headers (เช่น สำหรับ authentication)
- Data serialization
นี่คือตัวอย่างสำหรับ axios และ fetch:
- Axios
- Fetch
// Example using axios
import axios from 'axios';
export const client = axios.create({
baseURL: 'https://your-api-domain.com/api/',
timeout: 5000,
headers: { 'X-Custom-Header': 'my-custom-value' }
});
export const client = {
async post(endpoint: string, body: any, options?: RequestInit) {
const response = await fetch(`https://your-api-domain.com/api${endpoint}`, {
method: 'POST',
body: JSON.stringify(body),
...options,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'my-custom-value',
...options?.headers,
},
});
return response.json();
}
// ... other methods like put, delete, etc.
};
จัดระเบียบฟังก์ชัน API request แต่ละตัวของคุณใน shared/api/endpoints โดยจัดกลุ่มตาม API endpoint
เพื่อให้ตัวอย่างกระชับ เราจะละเว้นเรื่องการโต้ตอบกับ Form และ Validation สำหรับรายละเอียดเกี่ยวกับ Libraries เช่น Zod หรือ Valibot ให้ดูที่บทความ Type Validation and Schemas
import { client } from '../client';
export interface LoginCredentials {
email: string;
password: string;
}
export function login(credentials: LoginCredentials) {
return client.post('/login', credentials);
}
ใช้ไฟล์ index.ts ใน shared/api เพื่อ Export request functions ของคุณออกไป
export { client } from './client'; // ถ้าคุณต้องการ Export client เองด้วย
export { login } from './endpoints/login';
export type { LoginCredentials } from './endpoints/login';
Slice-Specific API Requests
ถ้า API request ถูกใช้โดย Slice ใด Slice หนึ่งเท่านั้น (เช่น หน้าเดียว หรือ Feature เดียว) และจะไม่ถูก Reuse ที่อื่น ให้วางมันไว้ใน api segment ของ Slice นั้นๆ วิธีนี้จะช่วยเก็บ Logic เฉพาะของ Slice ไว้ด้วยกันอย่างเป็นระเบียบ
- 📂 pages
- 📂 login
- 📄 index.ts
- 📂 api
- 📄 login.ts
- 📂 ui
- 📄 LoginPage.tsx
- 📂 login
import { client } from 'shared/api';
interface LoginCredentials {
email: string;
password: string;
}
export function login(credentials: LoginCredentials) {
return client.post('/login', credentials);
}
คุณไม่จำเป็นต้อง Export ฟังก์ชัน login() ออกไปใน Public API ของ Page เพราะไม่น่าจะมีที่อื่นในแอปที่ต้องการ Request นี้
หลีกเลี่ยงการวาง API calls และ Response types ไว้ใน Layer entities เร็วเกินไป การตอบกลับจาก Backend อาจแตกต่างจากที่ Frontend entities ของคุณต้องการ Logic API ใน shared/api หรือใน api segment ของ Slice ช่วยให้คุณแปลงข้อมูลได้อย่างเหมาะสม ทำให้ Entities โฟกัสไปที่เรื่องของ Frontend เท่านั้น
การใช้ Client Generators
ถ้า Backend ของคุณมี OpenAPI specification เครื่องมืออย่าง orval หรือ openapi-typescript สามารถ Generate API types และ Request functions ให้คุณได้ วางโค้ดที่ Generate มาไว้ใน เช่น shared/api/openapi และอย่าลืมใส่ README.md เพื่ออธิบายว่า Types พวกนี้คืออะไร และจะ Generate มันยังไง
การเชื่อมต่อกับ Server State Libraries
เมื่อใช้ Server State Libraries เช่น TanStack Query (React Query) หรือ Pinia Colada คุณอาจต้องแชร์ Types หรือ Cache keys ระหว่าง Slices ให้ใช้ shared layer สำหรับสิ่งต่าง ๆ เช่น:
- API data types
- Cache keys
- Common query/mutation options
สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานกับ Server State Libraries ให้ดูที่ React Query article