Trong bài viết này, chúng ta sẽ tạo một dự án Vue 3 kết hợp với các công cụ hỗ trợ khi làm việc với REST API:
- Axios: Thư viện gửi HTTP request với API trực quan và hỗ trợ Promise.
- Pinia: Quản lý trạng thái ứng dụng thay thế cho Vuex, đơn giản và hiệu quả.
- Vue Router: Xây dựng Single Page Application (SPA) với hệ thống định tuyến linh hoạt.
- TypeScript: Tăng cường khả năng bảo trì và mở rộng của code.
Khởi tạo dự án Vue 3
Trước tiên, hãy đảm bảo bạn đã cài đặt Node.js (ví dụ: Node.js v20.x).
Mở terminal, chuyển đến thư mục dự án và chạy lệnh:
npm create vue@latest
Quá trình này sẽ giúp bạn tạo dự án nhanh chóng với các tùy chọn sau (ví dụ):
Need to install the following packages:
create-vue@3.14.2
Ok to proceed? (y) y
> npx
> create-vue
Vue.js - The Progressive JavaScript Framework
✔ Project name: … vue-project
✔ Add TypeScript? … Yes
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … Yes
✔ Add Pinia for state management? … Yes
✔ Add Vitest for Unit Testing? … No
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? › Yes
✔ Add Prettier for code formatting? Yes
Scaffolding project in ./vue-project...
Done. Now run:
cd vue-project
npm install
npm run format
npm run dev
Sau khi tạo dự án, thực hiện các lệnh sau để cài đặt và chạy thử:
cd vue-project
npm install
npm run format
npm run dev
Xem thay đổi mã nguồn tại: Commit khởi tạo dự án.
Cài đặt Axios
Axios là thư viện hỗ trợ gửi HTTP request một cách dễ dàng. Cài đặt Axios bằng lệnh:
npm install axios
Sau đó, bạn có thể import và sử dụng trong các component Vue.
Xem thay đổi mã nguồn tại: Commit cài đặt Axios.
Cấu hình dự án
Cấu hình biến môi trường
Tạo tệp .env ở thư mục gốc dự án và định nghĩa biến môi trường, ví dụ:
VUE_API_URL=https://dummyjson.com
Biến này dùng để kết nối với API, ví dụ API của DummyJSON.
Lưu ý
Mặc định, Vite yêu cầu các biến môi trường phải bắt đầu bằng VITE_. Để sử dụng tiền tố khác, tùy chỉnh envPrefix trong tệp vite.config.ts:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig({
envPrefix: 'VUE', // Thay đổi tiền tố mặc định từ VITE thành VUE
plugins: [vue(), vueDevTools()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
Cấu hình Axios
Tạo một instance của Axios để sử dụng chung trong dự án.
Tạo thư mục services trong src và tạo tệp api.ts:
import axios from 'axios'
export const api = axios.create({
baseURL: import.meta.env.VUE_API_URL,
headers: {
'Content-Type': 'application/json',
},
})
Xem thay đổi mã nguồn tại: Commit cấu hình dự án
Tách biệt các dịch vụ
Để quản lý các API endpoint một cách gọn gàng, chúng ta tách các dịch vụ theo module.
Định nghĩa kiểu dữ liệu (Models)
Tạo tệp type.ts trong src/models/post:
export type Post = {
id: number
title: string
body: string
tags: string[]
reactions: {
likes: number
dislikes: number
}
views: number
userId: number
}
export interface Posts {
posts: Post[]
total: number
skip: number
limit: number
}
export type PostCreateRequest = {
title: string
body: string
tags: string[]
}
export type PostUpdateRequest = {
id: number
title?: string
body?: string
tags?: string[]
}
Tạo Service cho "Post"
Tạo thư mục services/post và trong đó tạo tệp index.ts để định nghĩa các API endpoint:
import { api } from '@/services/api'
import type {
Post,
Posts,
PostCreateRequest,
PostUpdateRequest,
} from '@/models/post/type'
const resource = '/posts'
const getPosts = async () => {
return await api.get<Posts>(resource)
}
const getPostById = async (id: number) => {
return await api.get<Post>(`${resource}/${id}`)
}
const createPost = async (payload: PostCreateRequest) => {
return await api.post<Post>(`${resource}/add`, payload)
}
const updatePost = async (payload: PostUpdateRequest) => {
return await api.put<Post>(`${resource}/${payload.id}`, payload)
}
const deletePost = async (id: number) => {
return await api.delete<Post>(`${resource}/${id}`)
}
export const postService = {
getPosts,
getPostById,
createPost,
updatePost,
deletePost,
}
Tổng hợp các dịch vụ
Để quản lý API dễ dàng, tạo tệp index.ts trong thư mục src/services:
import { postService } from '@/services/post'
export const API = {
post: postService,
}
Xem thay đổi mã nguồn tại: Commit tách biệt các dịch vụ
Tích hợp Service với Pinia Store
Tạo Store cho "Post"
Tạo thư mục stores trong src và tạo thư mục con post. Trong đó, tạo tệp index.ts với nội dung sau:
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { API } from '@/services'
import type { Posts } from '@/models/post/type'
import { isAxiosError } from 'axios'
export const usePostStore = defineStore('postStore', () => {
const posts = ref<Posts>({
posts: [],
total: 0,
skip: 0,
limit: 0,
})
const initPosts = (newPosts: Posts): void => {
posts.value = newPosts
}
const dispatchGetPosts = async (): Promise<void> => {
try {
const { data } = await API.post.getPosts()
initPosts(data)
} catch (error) {
if (isAxiosError(error)) {
switch (error.response?.status) {
case 400:
// 400 Bad Request
break
case 404:
// 404 Not Found
break
case 500:
// 500 Internal Server Error
break
default:
// Other error
break
}
}
throw error
}
}
return {
posts,
dispatchGetPosts,
}
})
Thực hiện tương tự với các action khác (getPostById, createPost, updatePost, deletePost v.v.)
Sử dụng Store trong Component
Ví dụ, tạo file src/views/PostView.vue để hiển thị danh sách bài viết:
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { usePostStore } from '@/stores/post'
import type { Posts } from '@/models/post/type'
const postStore = usePostStore()
const listPosts = computed<Posts>(() => postStore.posts)
const loading = ref<boolean>(false)
onMounted(async () => {
loading.value = true
try {
await postStore.dispatchGetPosts()
} catch (error) {
console.error('Error page:', error)
} finally {
loading.value = false
}
})
</script>
<template>
<div>
<h1>List Posts</h1>
<p v-if="loading">Loading...</p>
<div v-else>
<ul v-if="listPosts.posts.length">
<li v-for="post in listPosts.posts" :key="post.id">
{{ post.title }}
</li>
</ul>
</div>
</div>
</template>
Đăng ký tuyến đường (Router)
Cập nhật tệp src/router/index.ts để thêm tuyến đường cho trang bài viết:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
// ...
{
path: '/post',
name: 'post',
component: () => import('../views/post/PostsView.vue'),
},
// ...
],
})
export default router
Bây giờ bạn có thể truy cập và chạy thử dự án.
Xem thay đổi mã nguồn tại: Commit tích hợp Service với Pinia Store
Cấu trúc thư mục
vue-project/
│
├── .env # Các biến môi trường
├── src/
│ ├── models/
│ │ └── post/
│ │ └── type.ts # Kiểu dữ liệu cho "post"
│ │
│ ├── services/
│ │ ├── api.ts # Cấu hình instance Axios
│ │ ├── index.ts # Tổng hợp các dịch vụ
│ │ └── post/
│ │ └── index.ts # API endpoint cho "post"
│ │
│ ├── stores/
│ │ └── post/
│ │ └── index.ts # Store quản lý "post"
│ │
│ ├── views/
│ │ └── PostView.vue # Component hiển thị danh sách bài viết
│ │
│ └── router/
│ └── index.ts # Cấu hình định tuyến
│
└── vite.config.ts # Cấu hình Vite
Kết luận
Với cách tổ chức và cấu hình như trên, bạn có thể dễ dàng mở rộng dự án bằng cách bổ sung các module dịch vụ khác, chẳng hạn như users, teachers, courses, v.v.
Hướng dẫn này giúp đảm bảo rằng dự án của bạn được xây dựng theo tiêu chuẩn, có cấu trúc rõ ràng và dễ bảo trì trong suốt quá trình phát triển.
Hy vọng hướng dẫn này sẽ giúp bạn có một khởi đầu thuận lợi với dự án Vue 3!
Tài liệu tham khảo:
- Axios + Vue.js 3 + Pinia, a "comfy" configuration you can consider for an API REST
- Vue Axios Pinia Services
Mã nguồn tham khảo: Vue Project