Vue 3 + REST API + Swagger + VuetifyでCRUDアプリを作る|完成編

プログラミング

※ 当ブログ「日々是事始め(コレコト)」はプロモーションを含みます。

このシリーズの総まとめです。これまで学んだ「リアクティブ・コンポーネント・REST API・OpenAPI・Vuetify」を1つにつなげ、小さなタスク管理アプリ(CRUD)を組み立てます。完成済みの巨大なコードを貼るより、どの層に何を置くかという地図を持って帰ってもらうことを目的にします。

※ 学習用に調べた内容を個人的にまとめたものです。文章は AI の補助を受けて作成しています。


作るもの(CRUD)

  • Create:タスクを登録する
  • Read:一覧・詳細を表示する
  • Update:タスクを編集する
  • Delete:タスクを削除する

フォルダ構成(layerを意識する)

src/
├─ app/
│  ├─ router/index.ts        … 画面とURLの対応
│  └─ plugins/vuetify.ts     … Vuetify登録
├─ generated/api/            … OpenAPIから自動生成(手で触らない)
├─ shared/
│  └─ api/httpClient.ts      … axios共通設定
├─ features/
│  └─ tasks/
│     ├─ api/taskRepository.ts      … 生成APIを包む層
│     ├─ composables/useTasks.ts    … 画面が使うロジック
│     ├─ components/TaskForm.vue     … 入力フォーム部品
│     └─ pages/TaskListPage.vue      … 一覧ページ
└─ main.ts

キモは 「画面 → composable → repository → 生成API」 という一方向の流れです。生成 API を .vue から直接呼ばないことで、API 仕様が変わっても影響を repository に閉じ込められます。


① repository層:生成APIを包む

// features/tasks/api/taskRepository.ts
import { TasksApi, Configuration } from '@/generated/api'
import type { Task } from '@/generated/api'

const api = new TasksApi(
  new Configuration({ basePath: import.meta.env.VITE_API_BASE_URL })
)

export const taskRepository = {
  list:   async (): Promise<Task[]> => (await api.listTasks()).data,
  create: async (title: string): Promise<Task> =>
    (await api.createTask({ title, done: false })).data,
  remove: async (id: number): Promise<void> => {
    await api.deleteTask(id)
  },
}

② composable層:画面が使うロジック

// features/tasks/composables/useTasks.ts
import { ref } from 'vue'
import type { Task } from '@/generated/api'
import { taskRepository } from '../api/taskRepository'

export function useTasks() {
  const tasks = ref<Task[]>([])
  const loading = ref(false)
  const error = ref<string | null>(null)

  async function load() {
    loading.value = true
    error.value = null
    try {
      tasks.value = await taskRepository.list()
    } catch {
      error.value = '一覧の取得に失敗しました'
    } finally {
      loading.value = false
    }
  }

  async function add(title: string) {
    await taskRepository.create(title)
    await load()
  }

  return { tasks, loading, error, load, add }
}

③ 画面:Vuetifyで一覧と登録

<!-- features/tasks/pages/TaskListPage.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useTasks } from '../composables/useTasks'

const { tasks, loading, error, load, add } = useTasks()
const title = ref('')

onMounted(load)

async function onAdd() {
  if (!title.value) return
  await add(title.value)
  title.value = ''
}
</script>

<template>
  <v-container>
    <v-text-field v-model="title" label="タスク名" />
    <v-btn color="primary" @click="onAdd">追加</v-btn>

    <v-alert v-if="error" type="error">{{ error }}</v-alert>
    <v-progress-circular v-if="loading" indeterminate />

    <v-list>
      <v-list-item v-for="t in tasks" :key="t.id" :title="t.title" />
    </v-list>
  </v-container>
</template>

画面コンポーネントには、API の詳細も fetch も出てきません。useTasks() を呼んで状態とアクションを受け取るだけ。これが「読みやすくテストしやすい」状態です。残りの Update / Delete も同じ型(repository に足す → composable に足す → 画面で呼ぶ)で増やせます。


ここまでで身についたこと

  • 状態(リアクティブ)を起点に画面を組み立てる
  • 役割ごとにファイルを分け、一方向の流れを作る
  • OpenAPI から型安全な API クライアントを生成する
  • Vuetify で最後に見た目を整える

ロードマップに戻ると、まだ読んでいないクラスタ(Pinia・composables・API設計・Vuetifyフォーム)が残っています。気になるところから埋めていけば、知識が一本につながります。



もっと体系的に学ぶなら(書籍)

この記事はシリーズの一部です。TypeScript前提で Composition API・Pinia・Vue Router・テストまで一冊で体系的に学びたい方には、内容がそのまま重なる次の本がおすすめです。

関連記事

Vue 3を体系的に学ぶロードマップ|初心者がリアクティブ・REST API・Swagger連携・Vuetifyまで理解する手順
※ 当ブログ「日々是事始め(コレコト)」はプロモーションを含みます。Vue 3 を学び始めたとき、私がいちばん困ったのは「何から手をつければいいのか分からない」ことでした。リアクティブ、Composition API、Vue Router、...
OpenAPI GeneratorでTypeScript APIクライアントを生成する|Vue 3連携
※ 当ブログ「日々是事始め(コレコト)」はプロモーションを含みます。前の記事で「OpenAPI ファイルは API の設計図」だと整理しました。ここからが面白いところで、その設計図(openapi.yaml)から TypeScript の型...

PR

Back to top
タイトルとURLをコピーしました