Sử Dụng ofetch Để Gọi API Đơn Giản Và Mạnh Mẽ Hơn


Tuan Duc Tran

Xin chào mọi người!

Trong các ứng dụng web hiện đại, việc gọi API là một phần không thể thiếu. JavaScript đã có sẵn fetch() để thực hiện HTTP request, nhưng khi ứng dụng phát triển, chúng ta thường phải tự xử lý khá nhiều phần lặp lại như parse JSON, kiểm tra HTTP status, cấu hình baseURL, headers, timeout hoặc retry.

Đây là lúc ofetch trở nên hữu ích.

ofetch là một HTTP client hiện đại được xây dựng trên Fetch API, cung cấp API đơn giản hơn nhưng vẫn giữ được tính linh hoạt của fetch().

Thay vì viết:

const response = await fetch('/api/users');
 
if (!response.ok) {
	throw new Error(`HTTP ${response.status}`);
}
 
const users = await response.json();

Với ofetch, chúng ta có thể viết ngắn gọn hơn:

const users = await ofetch<User[]>('/api/users');

Đoạn code vẫn sử dụng Fetch API bên dưới, nhưng ofetch giúp loại bỏ nhiều boilerplate không cần thiết.

ofetch là gì?

ofetch là một HTTP client dựa trên Fetch API, được thiết kế để giúp việc thực hiện HTTP request thuận tiện và nhất quán hơn.

Một request JSON cơ bản:

import { ofetch } from 'ofetch';
 
const users = await ofetch<User[]>('/api/users');

ofetch có thể tự xử lý response JSON, vì vậy bạn không cần gọi response.json() thủ công.

Ngoài ra, ofetch còn cung cấp các tính năng như:

  • Tự động parse response.
  • Tự động throw error đối với HTTP response không thành công.
  • Query parameters.
  • Request body.
  • Timeout.
  • Retry.
  • baseURL.
  • Headers dùng chung.
  • Interceptors.
  • TypeScript generics.
  • Tạo instance với ofetch.create().

Cài đặt ofetch

Nếu sử dụng Bun:

bun add ofetch

Với npm:

npm install ofetch

Với pnpm:

pnpm install ofetch

Hoặc Yarn:

yarn add ofetch

Sau đó import ofetch:

import { ofetch } from 'ofetch';

GET request

Cách sử dụng đơn giản nhất là thực hiện một GET request:

const users = await ofetch<User[]>('/api/users');

Trong ví dụ này:

  • '/api/users' là endpoint.
  • User[] là kiểu dữ liệu mong đợi.
  • users chứa dữ liệu được trả về từ API.

Nếu API trả về một JSON array:

[
	{
		"id": 1,
		"name": "John"
	},
	{
		"id": 2,
		"name": "Jane"
	}
]

ofetch sẽ trả về dữ liệu JavaScript trực tiếp.

Không cần:

const response = await fetch('/api/users');
const users = await response.json();

POST request

ofetch cũng giúp gửi dữ liệu trong request body đơn giản hơn.

Ví dụ tạo một user:

const user = await ofetch<User>('/api/users', {
	method: 'POST',
	body: {
		name: 'John Doe',
		email: 'john@example.com',
	},
});

Bạn có thể truyền object trực tiếp vào body thay vì phải tự xử lý JSON thủ công.

PUT, PATCH và DELETE

Các HTTP method khác cũng được sử dụng theo cách tương tự.

PUT

const user = await ofetch<User>('/api/users/1', {
	method: 'PUT',
	body: {
		name: 'John Doe',
	},
});

PATCH

const user = await ofetch<User>('/api/users/1', {
	method: 'PATCH',
	body: {
		name: 'John Doe',
	},
});

DELETE

await ofetch('/api/users/1', {
	method: 'DELETE',
});

API của ofetch vẫn rất gần với Fetch API, vì vậy việc chuyển đổi từ fetch() sang ofetch tương đối đơn giản.

Query parameters

Khi API cần query parameters, bạn có thể sử dụng option query.

Ví dụ:

const users = await ofetch<User[]>('/api/users', {
	query: {
		page: 1,
		limit: 20,
		search: 'john',
	},
});

Thay vì tự xây dựng URL:

const url = `/api/users?page=${page}&limit=${limit}&search=${search}`;

ofetch sẽ xử lý phần query parameters cho bạn.

Điều này đặc biệt hữu ích khi có nhiều tham số hoặc các giá trị được tạo động.

Xử lý lỗi

Một điểm đáng chú ý của ofetch là HTTP response không thành công sẽ tạo ra lỗi.

Ví dụ:

try {
	const users = await ofetch<User[]>('/api/users');
} catch (error) {
	console.error(error);
}

Điều này khác với Fetch API ở chỗ fetch() không tự reject Promise chỉ vì server trả về HTTP status như 404 hoặc 500.

Với fetch() thông thường, bạn thường phải tự kiểm tra:

const response = await fetch('/api/users');
 
if (!response.ok) {
	throw new Error(`Request failed: ${response.status}`);
}

ofetch xử lý phần này giúp bạn, từ đó code gọi API có thể ngắn gọn và nhất quán hơn.

Timeout

Một request không nên chờ vô hạn trong nhiều trường hợp.

ofetch hỗ trợ timeout:

const users = await ofetch<User[]>('/api/users', {
	timeout: 5000,
});

Trong ví dụ trên, request có thời gian chờ tối đa là 5 giây.

Timeout đặc biệt hữu ích khi làm việc với API bên ngoài hoặc những request mà ứng dụng cần kiểm soát thời gian phản hồi.

Retry

ofetch cũng hỗ trợ retry cho các request phù hợp.

Ví dụ:

const users = await ofetch<User[]>('/api/users', {
	retry: 3,
});

Nếu request gặp một số lỗi có thể retry, ofetch có thể thử thực hiện request lại.

Tuy nhiên, retry cần được sử dụng thận trọng.

Đối với những request có side effect như POST, PATCH hoặc DELETE, việc retry không phù hợp có thể khiến một thao tác được thực hiện nhiều lần.

Vì vậy, retry nên được cân nhắc dựa trên loại request và khả năng idempotency của API.

Tạo API instance

Khi ứng dụng có nhiều API endpoint, việc tạo một instance dùng chung sẽ giúp giảm cấu hình lặp lại.

Ví dụ:

import { ofetch } from 'ofetch';
 
const api = ofetch.create({
	baseURL: '/api',
});

Sau đó:

const users = await api<User[]>('/users');
const user = await api<User>('/users/1');

Thay vì phải viết /api trong mọi request.

Đây là một cách tổ chức phù hợp khi ứng dụng có nhiều module sử dụng chung một API.

Shared headers

Bạn cũng có thể cấu hình headers dùng chung cho instance:

const api = ofetch.create({
	baseURL: '/api',
	headers: {
		Accept: 'application/json',
	},
});

Khi đó các request thông qua instance sẽ sử dụng cấu hình này.

Điều này giúp tập trung các thiết lập HTTP thay vì lặp lại chúng trong từng request.

Interceptors

ofetch hỗ trợ interceptors để xử lý request và response tại một nơi tập trung.

Ví dụ với onRequest:

const api = ofetch.create({
	baseURL: '/api',
 
	onRequest({ options }) {
		options.headers.set('Accept', 'application/json');
	},
});

Bạn cũng có thể sử dụng các lifecycle hook khác:

const api = ofetch.create({
	onRequest() {
		// Trước khi gửi request
	},
 
	onRequestError() {
		// Khi request gặp lỗi
	},
 
	onResponse() {
		// Khi nhận response
	},
 
	onResponseError() {
		// Khi response không thành công
	},
});

Interceptors đặc biệt hữu ích khi cần xử lý logic HTTP dùng chung cho toàn bộ ứng dụng.

TypeScript

Một trong những điểm thuận tiện khi sử dụng ofetch với TypeScript là có thể khai báo kiểu dữ liệu trả về ngay tại request.

Ví dụ:

interface User {
	id: number;
	name: string;
	email: string;
}
 
const users = await ofetch<User[]>('/api/users');

TypeScript sẽ biết users có kiểu User[].

Tương tự với một user duy nhất:

const user = await ofetch<User>('/api/users/1');

Cách này giúp API layer có type rõ ràng và giảm việc phải ép kiểu dữ liệu ở nhiều nơi.

Tạo API service

Khi ứng dụng lớn hơn, bạn có thể kết hợp ofetch với một service layer.

Ví dụ:

import { ofetch } from 'ofetch';
 
interface User {
	id: number;
	name: string;
	email: string;
}
 
interface CreateUserInput {
	name: string;
	email: string;
}
 
const api = ofetch.create({
	baseURL: '/api',
});
 
export const UserService = {
	getUsers() {
		return api<User[]>('/users');
	},
 
	getUser(id: number) {
		return api<User>(`/users/${id}`);
	},
 
	createUser(data: CreateUserInput) {
		return api<User>('/users', {
			method: 'POST',
			body: data,
		});
	},
};

Sau đó code phía ứng dụng chỉ cần:

const users = await UserService.getUsers();

hoặc:

const user = await UserService.getUser(1);

UI không cần biết chi tiết endpoint, HTTP method hay cấu hình request.

ofetch và Fetch API

ofetch không thay thế Fetch API bằng một cơ chế HTTP hoàn toàn khác.

Nó được xây dựng trên Fetch API và cung cấp một API thuận tiện hơn.

Có thể hình dung đơn giản:

Application
     ↓
   ofetch
     ↓
 Fetch API
     ↓
    HTTP

Vì vậy, nếu bạn đã quen với fetch(), việc sử dụng ofetch sẽ không yêu cầu phải học một mô hình HTTP hoàn toàn mới.

ofetch không phải thư viện quản lý server state

Đây là điểm quan trọng khi so sánh ofetch với những thư viện như SWR hoặc TanStack Query.

ofetch là HTTP client.

Nó tập trung vào việc thực hiện request và xử lý HTTP.

Trong khi đó, các thư viện như SWR hoặc TanStack Query tập trung vào những vấn đề ở tầng quản lý dữ liệu ứng dụng như:

  • Cache.
  • Revalidation.
  • Server state.
  • Query lifecycle.
  • Đồng bộ dữ liệu giữa các component.

Do đó, hai loại thư viện này có thể được sử dụng cùng nhau.

Có thể hình dung kiến trúc:

React UI
   ↓
SWR / TanStack Query
   ↓
  ofetch
   ↓
Fetch API
   ↓
  HTTP

Trong trường hợp này, SWR hoặc TanStack Query quản lý server state, còn ofetch đảm nhiệm HTTP request.

Khi nào nên sử dụng ofetch?

ofetch phù hợp khi bạn muốn một HTTP client nhẹ và có API đơn giản trên nền Fetch API.

Một số trường hợp phổ biến:

  • Ứng dụng frontend cần gọi REST API.
  • Backend cần thực hiện HTTP request tới service khác.
  • Muốn giảm boilerplate của fetch().
  • Muốn có baseURL và cấu hình dùng chung.
  • Muốn sử dụng timeout hoặc retry.
  • Muốn có interceptors.
  • Muốn tận dụng TypeScript generics cho API response.
  • Muốn sử dụng HTTP client nhưng không cần một thư viện quản lý server state.

Nếu ứng dụng cần cache và quản lý server state, bạn có thể kết hợp ofetch với một thư viện chuyên dụng thay vì cố gắng đưa những trách nhiệm đó vào HTTP client.

Kết luận

Fetch API đã đủ để thực hiện HTTP request, nhưng khi ứng dụng có nhiều API call, việc tự xử lý response, error, JSON parsing, query parameters và cấu hình dùng chung có thể tạo ra khá nhiều boilerplate.

ofetch giải quyết vấn đề này bằng cách cung cấp một API nhỏ gọn và quen thuộc dựa trên Fetch API.

Một request có thể đơn giản như:

const users = await ofetch<User[]>('/api/users');

Khi cần nhiều hơn, bạn có thể sử dụng:

const api = ofetch.create({
	baseURL: '/api',
	timeout: 5000,
});

Và tận dụng thêm query parameters, retry, interceptors, shared headers cùng TypeScript.

Điểm quan trọng là ofetch tập trung vào đúng một nhiệm vụ: làm cho việc thực hiện HTTP request trở nên đơn giản và nhất quán hơn.

Nếu bạn chỉ cần một HTTP client hiện đại trên nền Fetch API, ofetch có thể là một lựa chọn gọn gàng. Còn nếu ứng dụng cần cache, revalidation hoặc quản lý server state, ofetch có thể đóng vai trò HTTP layer bên dưới các thư viện chuyên dụng như SWR hoặc TanStack Query.