Skip to content

Lab: Type-safe API Response Handlers

ในแบบฝึกหัดนี้ คุณจะสร้างระบบ API response handling ที่ type-safe โดยใช้ Generics, Utility Types และ Type Guards

สร้าง type-safe API layer ที่:

  • รองรับ success และ error responses ด้วย generic types
  • มี type guards สำหรับตรวจสอบ response
  • ใช้ utility types สำหรับ CRUD operations
  • จัดการ pagination ได้
  1. สร้าง Generic API Response types

    สร้าง ApiSuccess<T> และ ApiError แล้วรวมเป็น ApiResponse<T>

  2. สร้าง Type Guards

    เขียน custom type guard isSuccess() และ isError()

  3. สร้าง CRUD Types ด้วย Utility Types

    ใช้ Omit, Partial, Pick สร้าง CreateInput, UpdateInput, ListItem

  4. สร้าง Generic Fetch Function

    เขียน fetchApi<T>() ที่ return ApiResponse<T>

  5. รวมทุกอย่างเข้าด้วยกัน

    สร้าง UserService ที่ใช้ทุก type ที่สร้างมา

Show Solution
// 1. Generic API Response types
interface ApiSuccess<T> {
ok: true;
data: T;
status: number;
}
interface ApiError {
ok: false;
error: string;
status: number;
details?: Record<string, string[]>;
}
type ApiResponse<T> = ApiSuccess<T> | ApiError;
// Paginated version
interface Paginated<T> {
items: T[];
total: number;
page: number;
perPage: number;
hasNext: boolean;
}
// 2. Type Guards
function isSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
return response.ok === true;
}
function isError<T>(response: ApiResponse<T>): response is ApiError {
return response.ok === false;
}
// 3. CRUD Types ด้วย Utility Types
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
type CreateUserInput = Omit<User, "id" | "createdAt" | "updatedAt">;
type UpdateUserInput = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;
type UserListItem = Pick<User, "id" | "name" | "email" | "role">;
// 4. Generic Fetch Function
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
try {
const res = await fetch(url);
const data = await res.json();
if (!res.ok) {
return { ok: false, error: data.message, status: res.status };
}
return { ok: true, data: data as T, status: res.status };
} catch (err) {
return { ok: false, error: "Network error", status: 0 };
}
}
// 5. UserService
async function getUsers(): Promise<UserListItem[]> {
const response = await fetchApi<Paginated<UserListItem>>("/api/users");
if (isError(response)) {
throw new Error(response.error);
}
return response.data.items;
}
async function getUser(id: number): Promise<User> {
const response = await fetchApi<User>(`/api/users/${id}`);
if (!isSuccess(response)) {
throw new Error(response.error);
}
return response.data;
}