API Integration¶
API Client — lib/api.ts¶
Tất cả API calls được tập trung trong file lib/api.ts. Sử dụng native fetch API.
const API = process.env.NEXT_PUBLIC_API
// Tạo khóa học mới
export async function createCourse(data) {
return fetch(API + "/courses", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
}).then(r => r.json())
}
// Upload file (presigned URL flow)
export async function upload(id, file) {
// 1. Lấy presigned URL từ backend
const r = await fetch(API + `/courses/${id}/upload-url`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
file_name: file.name,
content_type: file.type || "image/jpeg"
})
})
const { upload_url, file_key } = await r.json()
// 2. Upload trực tiếp lên R2
await fetch(upload_url, { method: "PUT", body: file })
// 3. Xác nhận upload hoàn tất
await fetch(API + `/courses/${id}/finalize-upload`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ file_key })
})
}
Upload Flow (3 bước)¶
sequenceDiagram
participant User
participant FE as Frontend
participant BE as Backend
participant R2 as Cloudflare R2
User->>FE: Chọn file
FE->>BE: POST /upload-url (file_name, content_type)
BE-->>FE: { upload_url, file_key }
FE->>R2: PUT upload_url (binary)
R2-->>FE: 200 OK
FE->>BE: POST /finalize-upload (file_key)
BE-->>FE: Updated course data
FE->>User: Hiển thị success Tại sao 3 bước?
- Bước 1: Backend tạo presigned URL có thời hạn (5 phút)
- Bước 2: File đi thẳng từ browser → R2, không qua backend (nhanh hơn, tiết kiệm bandwidth)
- Bước 3: Backend cập nhật database với key file đã upload
Quy tắc khi thêm API mới¶
- Thêm function vào
lib/api.ts— giữ tất cả API calls ở 1 chỗ - Luôn khai báo headers —
Content-Type: application/jsoncho JSON body - Handle errors — Check
response.oktrước khi parse JSON - Sử dụng TanStack Query — Cho data fetching, caching, và invalidation
Template thêm API call mới:¶
// lib/api.ts
export async function getUsers() {
const res = await fetch(API + "/users")
if (!res.ok) throw new Error("Failed to fetch users")
return res.json()
}
// Component
import { useQuery } from '@tanstack/react-query'
import { getUsers } from '@/lib/api'
const { data } = useQuery({
queryKey: ['users'],
queryFn: getUsers
})