Trong quá trình phát triển ứng dụng Vue.js, việc quản lý các yêu cầu HTTP đến API backend là một tác vụ thiết yếu. Axios là một thư viện HTTP client dựa trên Promise rất phổ biến, giúp đơn giản hóa việc gửi các yêu cầu này. Để đảm bảo tính nhất quán, dễ bảo trì và khả năng mở rộng, việc tùy chỉnh và đóng gói Axios là một phương pháp được khuyến nghị.
1. Cài đặt Axios
Trước tiên, hãy cài đặt Axios vào dự án của bạn:
npm install axios
2. Tổ chức mã nguồn: Tạo mô-đun HTTP Service
Để đóng gói Axios, chúng ta sẽ tạo một thư mục utils và một tệp httpService.js (hoặc apiClient.js) bên trong. Đây sẽ là nơi chứa tất cả logic cấu hình và các phương thức gửi yêu cầu HTTP.
your-vue-project/
├── src/
│ ├── components/
│ ├── views/
│ ├── router/
│ ├── store/
│ ├── utils/
│ │ └── httpService.js <-- Tệp này
│ └── main.js
└── ...
3. Cấu hình cơ bản cho Axios
Trong tệp httpService.js, chúng ta sẽ tạo một instance Axios với các cấu hình mặc định như URL cơ sở (baseURL) và thời gian chờ (timeout).
// src/utils/httpService.js
import axios from 'axios';
import qs from 'qs'; // Cần cài đặt: npm install qs
// Cấu hình URL cơ sở cho API của bạn
const API_BASE_URL = 'http://127.0.0.1:8080/api/v1/';
// Tạo một instance Axios tùy chỉnh
const apiClient = axios.create({
baseURL: API_BASE_URL,
timeout: 10000, // Thời gian chờ 10 giây
headers: {
// Mặc định Content-Type cho các yêu cầu POST/PUT/DELETE sử dụng qs.stringify
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
});
Lưu ý: Thư viện qs (querystring) được sử dụng để chuyển đổi đối tượng JavaScript thành chuỗi URL-encoded, phù hợp với application/x-www-form-urlencoded. Hãy đảm bảo bạn đã cài đặt nó: npm install qs.
4. Cấu hình Interceptors (Bộ chặn)
Interceptors cho phép bạn chặn và sửa đổi các yêu cầu hoặc phản hồi HTTP trước khi chúng được xử lý bởi then hoặc catch. Điều này cực kỳ hữu ích cho việc thêm token xác thực hoặc xử lý lỗi tập trung.
4.1. Bộ chặn Yêu cầu (Request Interceptor)
Thông thường, sau khi người dùng đăng nhập, một token xác thực sẽ được lưu trữ cục bộ (ví dụ: trong localStorage hoặc Vuex store). Bộ chặn yêu cầu sẽ tự động thêm token này vào tiêu đề của mỗi yêu cầu HTTP, giúp backend xác định phiên đăng nhập của người dùng.
// ... tiếp tục trong src/utils/httpService.js
// import store from '@/store'; // Nếu bạn sử dụng Vuex để quản lý token
apiClient.interceptors.request.use(
(config) => {
// Lấy token từ localStorage hoặc Vuex store
// Ví dụ lấy từ localStorage:
const authToken = localStorage.getItem('userAuthToken');
// Hoặc từ Vuex (nếu đã import store):
// const authToken = store.state.auth.token;
if (authToken) {
// Thêm token vào header Authorization theo định dạng Bearer
config.headers.Authorization = `Bearer ${authToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
4.2. Bộ chặn Phản hồi (Response Interceptor - Tùy chọn)
Bộ chặn phản hồi có thể được sử dụng để xử lý các lỗi HTTP toàn cục, ví dụ như hiển thị thông báo lỗi thân thiện với người dùng, hoặc chuyển hướng đến trang đăng nhập khi token hết hạn (status code 401).
// ... tiếp tục trong src/utils/httpService.js
apiClient.interceptors.response.use(
(response) => {
// Có thể kiểm tra và xử lý dữ liệu phản hồi chung ở đây
return response;
},
(error) => {
// Xử lý lỗi toàn cục
if (error.response) {
switch (error.response.status) {
case 401:
console.error('Không được ủy quyền: Token hết hạn hoặc không hợp lệ. Vui lòng đăng nhập lại.');
// Ví dụ: chuyển hướng đến trang đăng nhập
// router.push('/login');
break;
case 403:
console.error('Bạn không có quyền truy cập tài nguyên này.');
break;
case 404:
console.error('Không tìm thấy tài nguyên được yêu cầu.');
break;
case 500:
console.error('Lỗi máy chủ nội bộ. Vui lòng thử lại sau.');
break;
default:
console.error(`Lỗi HTTP ${error.response.status}:`, error.response.data);
}
} else if (error.request) {
console.error('Không nhận được phản hồi từ máy chủ:', error.request);
} else {
console.error('Lỗi khi thiết lập yêu cầu:', error.message);
}
return Promise.reject(error);
}
);
5. Đóng gói các phương thức HTTP
Để đơn giản hóa việc gọi API từ các component, chúng ta sẽ tạo các hàm bao bọc (wrapper functions) cho các phương thức HTTP phổ biến như GET, POST, PUT, DELETE.
// src/utils/httpService.js (hoàn chỉnh)
import axios from 'axios';
import qs from 'qs';
const API_BASE_URL = 'http://127.0.0.1:8080/api/v1/';
const apiClient = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
});
apiClient.interceptors.request.use(
(config) => {
const authToken = localStorage.getItem('userAuthToken');
if (authToken) {
config.headers.Authorization = `Bearer ${authToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
switch (error.response.status) {
case 401:
console.error('Không được ủy quyền: Token hết hạn hoặc không hợp lệ. Vui lòng đăng nhập lại.');
break;
case 403:
console.error('Bạn không có quyền truy cập tài nguyên này.');
break;
case 404:
console.error('Không tìm thấy tài nguyên được yêu cầu.');
break;
case 500:
console.error('Lỗi máy chủ nội bộ. Vui lòng thử lại sau.');
break;
default:
console.error(`Lỗi HTTP ${error.response.status}:`, error.response.data);
}
} else if (error.request) {
console.error('Không nhận được phản hồi từ máy chủ:', error.request);
} else {
console.error('Lỗi khi thiết lập yêu cầu:', error.message);
}
return Promise.reject(error);
}
);
export default {
/**
* Gửi yêu cầu GET
* @param {string} path - Đường dẫn API (ví dụ: '/users')
* @param {object} queryParams - Tham số truy vấn (ví dụ: { id: 1 })
*/
get(path, queryParams = {}) {
return apiClient.get(path, { params: queryParams });
},
/**
* Gửi yêu cầu POST
* Mặc định chuyển đổi data thành dạng x-www-form-urlencoded
* @param {string} path - Đường dẫn API (ví dụ: '/users')
* @param {object} data - Dữ liệu body gửi đi
* @param {object} config - Cấu hình Axios bổ sung
*/
post(path, data = {}, config = {}) {
// Sử dụng qs.stringify để gửi dữ liệu dạng form-urlencoded
return apiClient.post(path, qs.stringify(data), config);
},
/**
* Gửi yêu cầu PUT
* Mặc định chuyển đổi data thành dạng x-www-form-urlencoded
* @param {string} path - Đường dẫn API (ví dụ: '/users/1')
* @param {object} data - Dữ liệu body gửi đi
* @param {object} config - Cấu hình Axios bổ sung
*/
put(path, data = {}, config = {}) {
return apiClient.put(path, qs.stringify(data), config);
},
/**
* Gửi yêu cầu DELETE
* @param {string} path - Đường dẫn API (ví dụ: '/users/1')
* @param {object} data - Dữ liệu body gửi đi (nếu API backend yêu cầu)
* @param {object} config - Cấu hình Axios bổ sung
*/
remove(path, data = {}, config = {}) {
// DELETE request có thể gửi dữ liệu qua body (dùng data) hoặc qua params
// Theo bản gốc, nó gửi data qua body dạng x-www-form-urlencoded
return apiClient.delete(path, { data: qs.stringify(data), ...config });
}
};
6. Tích hợp vào ứng dụng Vue.js
Để dễ dàng sử dụng mô-đun HTTP service này trong tất cả các component Vue, bạn có thể gắn nó vào prototype của Vue trong tệp main.js.
// src/main.js
import { createApp } from 'vue'; // Hoặc Vue from 'vue' cho Vue 2
import App from './App.vue';
import router from './router';
import store from './store'; // Nếu bạn có Vuex store
import httpService from './utils/httpService'; // Import mô-đun HTTP service của bạn
const app = createApp(App); // Hoặc new Vue({ ... }) cho Vue 2
app.use(router);
app.use(store);
// Gắn httpService vào prototype của Vue
// Từ giờ bạn có thể truy cập nó qua `this.$http` trong bất kỳ component nào
app.config.globalProperties.$http = httpService; // Đối với Vue 3
// Vue.prototype.$http = httpService; // Đối với Vue 2
app.mount('#app');
7. Cách sử dụng trong component Vue
Bây giờ, bạn có thể dễ dàng gửi các yêu cầu HTTP từ bất kỳ component Vue nào bằng cách sử dụng this.$http.
<template>
<div>
<button @click="fetchUserData">Tải Dữ Liệu Người Dùng</button>
<pre>{{ userData }}</pre>
</div>
</template>
<script>
export default {
data() {
return {
userData: null,
error: null,
};
},
methods: {
async fetchUserData() {
try {
const userId = 123; // Ví dụ ID người dùng
const response = await this.$http.get('/users', { id: userId });
this.userData = response.data;
console.log('Dữ liệu người dùng:', response.data);
} catch (err) {
this.error = 'Không thể tải dữ liệu người dùng.';
console.error('Lỗi khi tải dữ liệu người dùng:', err);
}
},
async createNewPost() {
try {
const postData = {
title: 'Bài viết mới',
body: 'Nội dung của bài viết mới.',
userId: 1,
};
const response = await this.$http.post('/posts', postData);
console.log('Tạo bài viết thành công:', response.data);
} catch (err) {
console.error('Lỗi khi tạo bài viết:', err);
}
}
},
// Có thể gọi fetchUserData khi component được mounted
// mounted() {
// this.fetchUserData();
// }
};
</script>