frontend-api-integration-patterns – skill Thiết kế & UI cho agent AI
Mẫu sẵn sàng cho production để tích hợp ứng dụng frontend với API backend, gồm xử lý race condition, hủy request, chiến lược retry, chuẩn hóa lỗi và quản lý trạng thái UI.
Cài skill frontend-api-integration-patterns bằng một lệnh
Chọn công cụ bạn đang dùng, copy lệnh và dán vào terminal. Skill sẽ nằm trong thư mục skills của công cụ đó và sẵn sàng dùng ở phiên làm việc tiếp theo.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-api-integration-patterns-d18ba.tgz | tar xz -C ~/.claude/skills
Gọi trong Claude Code: /frontend-api-integration-patterns – rồi mô tả công việc bạn muốn làm.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-api-integration-patterns-d18ba.tgz | tar xz -C ~/.codex/skills
Gọi trong Codex: $frontend-api-integration-patterns – rồi mô tả công việc bạn muốn làm.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-api-integration-patterns-d18ba.tgz | tar xz -C ~/.hermes/skills
Gọi trong Hermes Agent: /frontend-api-integration-patterns – rồi mô tả công việc bạn muốn làm.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/frontend-api-integration-patterns-d18ba.tgz | tar xz -C ~/.deepcode/skills
Gọi trong DeepSeek: /frontend-api-integration-patterns – rồi mô tả công việc bạn muốn làm.
Nội dung SKILL.md
Nội dung SKILL.md gốc bằng tiếng Anh.
Frontend API Integration Patterns
Overview
This skill provides production-ready patterns for integrating frontend applications with backend APIs.
Most frontend issues are not caused by APIs being difficult to call, but by incorrect handling of asynchronous behavior—leading to race conditions, stale data, duplicated requests, and poor user experience.
This skill focuses on correctness, resilience, and user experience, not just making API calls work.
---
When to Use This Skill
- Connecting frontend apps (React, React Native, Vue, etc.) to backend APIs
- Integrating ML/AI endpoints (
/predict,/recommend) - Handling asynchronous data in UI
- Fixing stale data, flickering UI, or duplicate requests
- Designing scalable frontend API layers
---
Core Patterns
1. API Layer (Separation of Concerns)
Centralize API logic and normalize errors.
export class ApiError extends Error {
constructor(message, status, payload = null) {
super(message);
this.name = "ApiError";
this.status = status;
this.payload = payload;
}
}
export const apiClient = async (url, options = {}) => {
const res = await fetch(url, {
headers: { "Content-Type": "application/json" },
...options,
});
if (!res.ok) {
let payload = null;
try {
payload = await res.json();
} catch (_) {}
throw new ApiError(
payload?.message || "Request failed",
res.status,
payload
);
}
// handle empty responses safely (e.g. 204 No Content)
if (res.status === 204) return null;
const text = await res.text();
return text ? JSON.parse(text) : null;
};---
2. Race-Safe State Management
Prevent stale responses from overwriting fresh data.
useEffect(() => {
let cancelled = false;
const load = async () => {
try {
setLoading(true);
setError(null);
const result = await getUser();
if (!cancelled) setData(result);
} catch (err) {
if (!cancelled) setError(err.message);
} finally {
if (!cancelled) setLoading(false);
}
};
load();
return () => {
cancelled = true;
};
}, []);> Use a cancellation flag for non-fetch async logic. For network requests, prefer AbortController.
---
3. Request Cancellation (AbortController)
Cancel in-flight requests to avoid memory leaks and stale updates.
useEffect(() => {
const controller = new AbortController();
const load = async () => {
try {
const data = await getUser({ signal: controller.signal });
setData(data);
} catch (err) {
if (err.name === "AbortError") return;
setError(err.message);
}
};
load();
return () => controller.abort();
}, [userId]);---
4. Retry with Exponential Backoff
Retry only explicitly replay-safe operations and classified transient failures. The default performs one attempt. Do not classify arbitrary programming or parsing exceptions as network errors. Honor provider Retry-After and a total deadline in the real adapter; do not enable this simple backoff for rate-limit responses. The POST prediction example intentionally does not enable retries unless the backend contract independently guarantees replay safety.
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const fetchWithBackoff = async (fn, retries = 3, delay = 300, replaySafe = false) => {
try {
return await fn();
} catch (err) {
const isAbort = err.name === "AbortError";
const isHttpError = typeof err.status === "number";
const isNetworkFailure = err.code === "NETWORK_ERROR";
const isRetryable = replaySafe && !isAbort &&
(isNetworkFailure || (isHttpError && err.status >= 500));
if (retries <= 0 || !isRetryable) throw err;
const nextDelay = delay * 2 + Math.random() * 100;
await sleep(nextDelay);
return fetchWithBackoff(fn, retries - 1, nextDelay, replaySafe);
}
};---
5. Debounced API Calls
Avoid excessive API calls (e.g., search inputs).
const useDebounce = (value, delay = 400) => {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(t);
}, [value, delay]);
return debounced;
};---
6. Request Deduplication
Prevent duplicate API calls across components.
const inFlight = new Map();
export const dedupedFetch = (key, fn) => {
if (inFlight.has(key)) return inFlight.get(key);
const promise = fn().finally(() => inFlight.delete(key));
inFlight.set(key, promise);
return promise;
};---
Examples
Example 1: ML Prediction with Cancellation
const controllerRef = useRef(null);
const handlePredict = async (input) => {
controllerRef.current?.abort();
const controller = new AbortController();
controllerRef.current = controller;
try {
const result = await fetchWithBackoff(() =>
apiClient("/predict", {
method: "POST",
body: JSON.stringify({ text: input }),
signal: controller.signal,
})
);
if (!controller.signal.aborted && controllerRef.current === controller) setOutput(result);
} catch (err) {
if (err.name === "AbortError") return;
if (controllerRef.current === controller) setError(err.message);
}
};---
Example 2: Debounced Search
const debouncedQuery = useDebounce(query, 400);
useEffect(() => {
if (!debouncedQuery) return;
const controller = new AbortController();
searchAPI(debouncedQuery, { signal: controller.signal })
.then(setResults)
.catch((err) => {
if (err.name !== "AbortError") {
setError("Search failed. Please try again.");
}
});
return () => controller.abort();
}, [debouncedQuery]);---
Example 3: Optimistic UI Update
const deleteItem = async (id) => {
const previous = items;
setItems((curr) => curr.filter((item) => item.id !== id));
try {
await apiClient(`/items/${id}`, { method: "DELETE" });
} catch (err) {
setItems(previous);
setError("Delete failed. Please try again.");
}
};---
Best Practices
- ✅ Centralize API logic in a dedicated layer
- ✅ Normalize errors using a custom error class
- ✅ Always handle loading, error, and success states
- ✅ Use AbortController for request cancellation
- ✅ Retry only transient failures (5xx)
- ✅ Use debouncing for input-driven APIs
- ✅ Deduplicate identical requests
---
Anti-Patterns
- ❌ Retrying 4xx errors
- ❌ No request cancellation (memory leaks)
- ❌ Race-condition-prone state updates
- ❌ Swallowing errors silently
- ❌ Global loading/error state for multiple requests
- ❌ Calling APIs directly inside components repeatedly
---
Common Pitfalls
Problem: UI shows stale data
Solution: Use cancellation or guard against outdated responses
Problem: Too many API calls on input
Solution: Use debouncing + cancellation
Problem: Duplicate requests from multiple components
Solution: Use request deduplication
Problem: Server overload during retry
Solution: Use exponential backoff
Problem: State updates after component unmount
Solution: Use AbortController cleanup
---
Limitations
- These examples use vanilla JavaScript patterns; adapt them to your framework's data-fetching library when using React Query, SWR, Apollo, Relay, or similar tools.
- Do not retry non-idempotent mutations unless the backend provides idempotency keys or another duplicate-safe contract.
- Do not expose privileged API keys in frontend code; proxy sensitive requests through a backend.
---
Additional Resources
- https://developer.mozilla.org/en-US/docs/Web/API/AbortController
- https://react.dev
- https://axios-http.com
---
Skill liên quan
- frontend-data-contracts – Kỷ luật độc lập framework về type safety tại ranh giới mạng của mọi ứng dụng React hoặc…
- frontend-design – Thiết kế và triển khai giao diện frontend nổi bật trong khuôn khổ thương hiệu, khả năng…
- frontend-dev-guidelines – Bạn là kỹ sư frontend cấp cao, làm việc theo tiêu chuẩn kiến trúc và hiệu năng nghiêm…
- frontend-lighthouse – Thêm cổng Lighthouse CI có thể tái sử dụng cho bản build frontend production, với ngân…
- frontend-mobile-development-component-scaffold – Khởi tạo khung component React và React Native với TypeScript, khả năng truy cập, test…
- frontend-observability – Mẫu quan sát (observability) phía client độc lập framework, có thể tái sử dụng cho tích…
- frontend-optimistic-mutations – Kỷ luật độc lập framework cho luồng ghi (write path) của mọi ứng dụng React hoặc React…
- frontend-seo – Hệ thống SEO độc lập framework, dùng được cho mọi frontend React hoặc React…