Cập nhật ·  

Xây dựng dự án Vue 3

Hướng dẫn chi tiết cách thiết lập dự án Vue 3 với Axios, Pinia và Vue Router - những công cụ hữu ích giúp tối ưu hóa quá trình làm việc với REST API.
Minh Lâm

Minh Lâm

@minhlam1996vn

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ụ:

.env.
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:

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:

src/services/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:

src/models/post/type.ts.
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:

src/services/post/index.ts.
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:

src/service/index.ts.
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:

src/stores/post/index.ts.
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:

src/views/PostView.vue.
<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:

src/router/index.ts.
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:

Mã nguồn tham khảo: Vue Project