💬

Nuxt3とFirebaseでスレッド作成機能を実装する方法

に公開

はじめに

この記事では、Nuxt3とFirebaseを使用してスレッド作成機能を実装する方法について説明します。Vue 3のComposition API、Piniaストア、Vuetify UIコンポーネントを活用した実装例を紹介します。

実装の概要

スレッド作成機能は以下の要素で構成されています:

  • ページコンポーネント (app/pages/thread/create.vue)
  • フォームコンポーネント (app/components/ThreadForm.vue)
  • ストア (app/stores/thread.ts)

1. ページコンポーネントの実装

<script setup lang="ts">
const { createThread } = useThreadStore()
const error = ref('')

const handleSubmit = async (data: { title: string; description: string }) => {
  const { error: threadError } = await createThread(
    data.title,
    data.description
  )

  if (threadError) {
    error.value = threadError.message
    return
  }
  await navigateTo('/thread')
}
</script>

<template>
  <v-card class="mx-auto" max-width="600">
    <v-card-item>
      <v-card-title> スレッド作成 </v-card-title>
    </v-card-item>
    <v-card-item>
      <v-alert
        v-if="error"
        type="error"
        :text="error"
        variant="tonal"
        class="mb-4"
      />
      <ThreadForm @submit="handleSubmit" />
    </v-card-item>
  </v-card>
</template>

ポイント

  • エラーハンドリング: スレッド作成時にエラーが発生した場合、アラートで表示
  • ナビゲーション: 作成成功後は自動的にスレッド一覧ページに遷移
  • UI: Vuetifyのカードコンポーネントを使用してモダンなデザイン

2. フォームコンポーネントの実装

<script setup lang="ts">
const form = ref()
const title = ref('')
const description = ref('')

const titleRules = [(v: string) => !!v || 'タイトルは必須です']
const descriptionRules = [(v: string) => !!v || '概要は必須です']

const emit = defineEmits<{
  submit: [data: { title: string; description: string }]
}>()

const submit = async () => {
  const { valid } = await form.value.validate()
  if (valid) {
    emit('submit', {
      title: title.value,
      description: description.value,
    })
  }
}
</script>

<template>
  <v-form ref="form" @submit.prevent="submit">
    <v-text-field
      v-model="title"
      label="タイトル"
      variant="underlined"
      :rules="titleRules"
      required
    />

    <v-textarea
      v-model="description"
      label="概要"
      variant="underlined"
      :rules="descriptionRules"
      required
      rows="4"
    />

    <v-btn color="primary" class="mt-4" block type="submit">作成</v-btn>
  </v-form>
</template>

ポイント

  • バリデーション: タイトルと概要の必須チェック
  • イベント発火: 親コンポーネントにsubmitイベントを発火
  • UI: Vuetifyのフォームコンポーネントで統一感のあるデザイン

3. ストアの実装

export interface Thread {
  id: string
  title: string
  description: string
  authorId: string
  authorName: string
  createdAt: Date
  updatedAt: Date
}

export const useThreadStore = defineStore('thread', () => {
  const { $firestore } = useNuxtApp()
  const firestore = $firestore as Firestore | null
  const threads = ref<Thread[]>([])
  const loading = ref(false)

  // スレッドを作成
  const createThread = async (title: string, description: string) => {
    if (!firestore) {
      return {
        thread: null,
        error: new Error('Firestoreが初期化されていません'),
      }
    }

    const authStore = useAuthStore()
    const profileStore = useProfileStore()

    if (!authStore.user || !profileStore.profile) {
      return {
        thread: null,
        error: new Error(
          'ユーザーが認証されていないか、プロフィールが設定されていません'
        ),
      }
    }

    loading.value = true
    try {
      const threadData = {
        title,
        description,
        authorId: authStore.user.uid,
        authorName: profileStore.profile.nickname,
        createdAt: serverTimestamp(),
        updatedAt: serverTimestamp(),
      }

      const docRef = await addDoc(collection(firestore, 'threads'), threadData)

      const newThread: Thread = {
        id: docRef.id,
        ...threadData,
        createdAt: new Date(),
        updatedAt: new Date(),
      }

      threads.value.unshift(newThread)

      return { thread: newThread, error: null }
    } catch (error) {
      return { thread: null, error: error as Error }
    } finally {
      loading.value = false
    }
  }

  return {
    threads,
    loading,
    createThread,
    getThreads,
  }
})

ポイント

  • 型安全性: TypeScriptのインターフェースでデータ構造を定義
  • エラーハンドリング: 各段階でのエラーチェックと適切なエラーメッセージ
  • 認証チェック: ユーザー認証とプロフィール設定の確認
  • リアルタイム更新: 作成後すぐにローカルストアを更新

4. 実装の流れ

  1. フォーム入力: ユーザーがタイトルと概要を入力
  2. バリデーション: 必須項目のチェック
  3. 認証確認: ユーザー認証とプロフィール設定の確認
  4. Firestore保存: スレッドデータをFirestoreに保存
  5. ローカル更新: ストアの状態を更新
  6. 画面遷移: スレッド一覧ページに遷移

5. 技術スタック

  • フロントエンド: Nuxt3, Vue 3, TypeScript
  • UI: Vuetify 3
  • 状態管理: Pinia
  • バックエンド: Firebase Firestore
  • 認証: Firebase Authentication

まとめ

この実装では、モダンなVue.jsの機能を活用して、ユーザーフレンドリーなスレッド作成機能を構築しています。型安全性、エラーハンドリング、UI/UXの観点から、実用的なアプリケーションに必要な要素を網羅しています。

特に、Composition APIとPiniaストアの組み合わせにより、保守性の高いコードを実現しており、今後の機能拡張にも対応しやすい構造になっています。

GitHubで編集を提案

Discussion