VueJS/Nuxt

[VueJS] Nuxt3에서 파이어베이스 사용해보기(+TS)

SongMinu 2023. 7. 2. 18:47
728x90

전에 vite, vue3, typescript와 파이어베이스를 이용해 프로젝트를 만들었었다.(https://minu0807.tistory.com/154)

최근 시간 날 때 nuxt3를 공식문서를 보면서 조금씩 이용해 보다가 vuefire라는 걸 이용해 파이어베이스를 쉽게 사용할 수 있는 것 같아 한 번 사용해보려고 했으나.... 생각보다 불편해서 그냥 firebase 패키지만 사용했다.

정확한 이유는 본문 맨 마지막에 작성...

이 글에서는 파이어베이스-파이어스토어에 있는 데이터를 가져오는 것까지만 작성했다.

본문에서 작업한 프로젝트는

https://minu0807.tistory.com/157

https://minu0807.tistory.com/158

 

[VueJS] Nuxt3 설치 및 layouts, pages 적용(compositionAPI)

프로젝트 설치 npx nuxi init 공식문서에 나와 있는 설치 방법으로 설치를 하면 이렇게 생성이 된다. nuxt2에서는 설치 시 기본적인 디렉터리들을 생성해줬었는데 nuxt3는 없다. 처음 설치하고 봤을 때

minu0807.tistory.com

 

[VueJS] Nuxt3에 quasar 설치 및 적용(+sass, icon)

넉스트3를 하나하나 알아보기 위해 최근 설치했다. 알아보기 위해 설치했는데 컴포넌트나 디자인 같은 것들을 하나하나 만들기엔 너무 많은 시간이 소요될 것 같아 디자인 프레임워크를 찾아봤

minu0807.tistory.com

이 2개를 진행한 이후에 작업했다.

설치

npm i firebase

.env 생성

파이어베이스 설정 값들을 공개할 순 없기에 .env에 작성을 했다.

FIREBASE_API_KEY=
FIREBASE_AUTH_DOMAIN=
FIREBASE_DATABASE_RUL=
FIREBASE_PROJECT_ID=
FIREBASE_STORAGE_BUCKET=
FIREBASE_MESSAGING_SENDER_ID=
FIREBASE_APP_ID=
FIREBASE_MEASUREMENT_ID=

이렇게 생성 후 파이어베이스 프로젝트 설정에 있는 값을 넣는다.

nuxt.config.ts 수정

env 파일에 작성한 값을 읽어와 사용하기 위해 따로 작업을 해줘야 한다.

파일 안에 값을 가져와 사용하려면 runtimeConfig 옵션을 활용해야 한다.

export default defineNuxtConfig({
  modules: ['nuxt-quasar-ui'],
  quasar: {
    lang: 'ko-KR',
    extras: {
      fontIcons: ['material-icons'],
    },
  },
  runtimeConfig: {
    public: {
      FB_API_KEY: process.env.FIREBASE_API_KEY,
      FB_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN,
      FB_DATABASE_URL: process.env.FIREBASE_DATABASE_RUL,
      FB_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
      FB_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET,
      FB_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID,
      FB_APP_ID: process.env.FIREBASE_APP_ID,
      FB_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID,
    },
  },
});

https://nuxt.com/docs/guide/going-further/runtime-config#runtime-config

 

Runtime Config · Nuxt Advanced

Nuxt provides a runtime config API to expose configuration within your application and server routes, with the ability to update it at runtime by setting environment variables. To expose config and environment variables to the rest of your app, you will ne

nuxt.com

 

초기화 작업

파이어베이스앱 설정 초기화의 경우 한 번만 실행되면 되기 때문에 plugins 디렉터리에 만들어서 처리하려고 했으나 진행 중 발생한 문제를 도저히 해결을 못하겠어서 고민하다 composables 방식으로 변경했다.

Error: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp().
이거랑 방법을 바꿀 때마다 다른 종류의 에러들이 계속 발생했었다.

프로젝트에 composables 디렉터리를 생성 후 useFirebase.ts 파일 생성 후 아래와 같이 소스코드를 작성

import { initializeApp, getApps } from 'firebase/app';
import type { FirebaseApp } from 'firebase/app';

export const useFirebaseApp = (): FirebaseApp => {
  const config = useRuntimeConfig();
  let app: FirebaseApp;
  if (!getApps().length) {
    app = initializeApp({
      apiKey: config.public.FB_API_KEY,
      appId: config.public.FB_APP_ID,
      authDomain: config.public.FB_AUTH_DOMAIN,
      databaseURL: config.public.FB_DATABASE_URL,
      measurementId: config.public.FB_MEASUREMENT_ID,
      messagingSenderId: config.public.FB_MESSAGING_SENDER_ID,
      projectId: config.public.FB_PROJECT_ID,
      storageBucket: config.public.FB_STORAGE_BUCKET,
    });
  } else {
    app = getApps()[0];
  }
  return app;
};

getApps() 함수는 이미 초기화된 파이어베이스앱이 있는지 확인할 수 있다.

이 함수를 사용해서 이미 초기화된 파이어베이스앱이 있으면 다시 초기화 시키지 않고, 생성된 파이어베이스 앱을 전달하게끔 할 수 있도록 작성했다.

https://nuxt.com/docs/guide/directory-structure/composables

 

composables/ · Nuxt Directory Structure

Nuxt 3 uses the composables/ directory to automatically import your Vue composables into your application using auto-imports! Under the hood, Nuxt auto generates the file .nuxt/imports.d.ts to declare the types. Be aware that you have to run nuxi prepare,

nuxt.com

파이어스토어 데이터베이스에서 데이터 가져오기

composables 디렉터리에 useFirestoreDB.ts를 생성 후 아래와 같이 소스코드 작성

import { getFirestore, collection, getDocs } from 'firebase/firestore';
import type { DocumentData } from 'firebase/firestore';

export const getFirestoreData = async (
  collectionName: string,
): Promise<(DocumentData & { id: string })[] | []> => {
  let result: (DocumentData & { id: string })[] = [];
  try {
    if (!collectionName) throw new Error('Need CollectionName.');

    const querySnapshot = await getDocs(collection(db(), collectionName));
    if (!querySnapshot.empty) {
      result = querySnapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));
    }
  } catch (err) {
    console.error(err);
    throw new Error('데이터 가져오기 실패');
  }
  return result;
};

파이어스토어에서 데이터를 가져오는 걸 공통으로 사용하기 위해 컴포저블 함수로 만들었다.

가져올 컬렉션 이름만 넘겨주면 해당 컬렉션에 데이터가 있으면 모든 데이터를 넘겨주고, 없으면 빈 배열을 반환한다.

추후에 옵션 파라미터로 where을 넘겨서 조건식으로 데이터를 가져올 수 있도록 함수를 수정할 생각이다.

현재 위 소스는 파라미터로 받은 컬렉션네임의 컬렉션에 있는 모든 데이터를 가져온다.

그리고 추후에 id를 사용할 것 같아서 id와 data를 동일한 레벨상에 담게 했다.

처음엔 { id: string, data: { DocumentData } } 이런 형식으로 데이터 구조를 만들었었다.

퀘이사 테이블 컴포넌트에 데이터를 맵핑할 때 1뎁스 이상은 맵핑이 안돼서 이렇게 만들었다.

 

데이터 가져와서 화면에 그리기

아래 소스는 위에 만든 컴포저블 함수를 이용해 데이터를 가져와서 화면에 뿌려준다.

<script setup lang="ts">
import { onMounted, ref } from 'vue';
import type { Ref } from 'vue';
import type { DocumentData } from 'firebase/firestore';
import type { QTableColumn } from 'quasar';

let lists: Ref<{ id: string; data: DocumentData }[] | []> = ref([]);

onMounted(async () => {
  lists.value = await getFirestoreData('test-board');
});

const columns: QTableColumn[] = [
  {
    name: 'title',
    label: '제목',
    field: 'title',
    align: 'left',
    headerStyle: 'width: 20%',
    sortable: true,
  },
  {
    name: 'wireter',
    label: '작성자',
    field: 'writer',
    align: 'center',
    sortable: true,
  },
  {
    name: 'createdAt',
    label: '작성일',
    field: 'createdAt',
    align: 'center',
    sortable: true,
    format(val, _row) {
      return val.toDate().toLocaleString();
    },
  },
  {
    name: 'viewer',
    label: '조회수',
    field: 'viewer',
    align: 'center',
    sortable: true,
  },
];
</script>
<template>
  <q-card dark bordered class="bg-brown-7 my-card">
    <q-card-section>
      <div class="text-h6">Firebase / Firestore Database</div>
      <div class="text-subtitle2">firestore</div>
    </q-card-section>

    <q-separator dark inset />

    <q-card-section>
      파이어베이스에서 제공하는 Firestore Database를 사용해보는 화면
    </q-card-section>
  </q-card>

  <q-separator class="q-mt-md q-mb-xs" />

  <div class="q-pa-md">
    <q-table
      bordered
      title=""
      :rows="lists"
      :columns="columns"
      no-data-label="No Data."
      row-key="name"
    />
  </div>
</template>

퀘이사의 테이블 컴포넌트를 사용했다.

field 부분에서 화면에 보여줄 데이터를 맵핑하면 된다.

 

 

결과 화면

퀘이사 테이블 컴포넌트에 데이터가 출력된 모습


vuefire를 사용하지 않은 이유

파이어베이스를 사용하는데 vuefire만 쓰는게 아니라 firebase와 같이 섞어서 사용해야 하는 게

굳이 이럴 거면 firebase 패키지만 쓰는 게 더 편한 것 같은데?라는 생각이 쓸대마다 점점 크게 느껴졌다.

<script setup>
import { useCollection } from 'vuefire'
import { collection } from 'firebase/firestore'

const todos = useCollection(collection(db, 'todos'))
</script>

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
     <span>{{ todo.text }}</span>
    </li>
  </ul>
</template>

이게 vuefire 공식문서에 있는 파이어스토어 컬렉션 데이터를 가져오는 예제 소스이다.

firebase/firestore의 collection과 vuefire의 useCollection을 써야 한다.

사용자가 2개를 선언해서 사용할게 아니라 vuefire 패키지 내에서 firebase의 함수를 사용해 처리해 줬으면 어땠을까 하는 생각이 많이 들었다.

 

그리고 가장 큰 문제는 타입지정이었다.

공식문서도 js 방식의 예제들 뿐이라 소스를 하나하나 들어가서 내가 사용하는 함수가 어떤 타입을 리턴하는지, 어떤 타입의 파라미터를 받는지 등등 확인을 해보는데 한계는 좀 느꼈었다.

import { collection, query } from 'firebase/firestore';
import type { DocumentData } from 'firebase/firestore';
import { useFirestore, useCollection } from 'vuefire';
import type { _RefFirestore } from 'vuefire';

export const getFirestoreData = (collectionName: string, query?: string) => {
  const db = useFirestore();
  let result: _RefFirestore<DocumentData[]>;
  if (!collectionName) throw 'Need CollectionName.';
  if (!query) {
    result = useCollection(collection(db, collectionName));
  } else {
    result = useCollection(collection(db, collectionName));
  }
  return result;
};

소스를 커밋하지 않고 계속 수정을 했어서 유일하게 커밋했던 게 이 소스인데...

컴포저블함수로 처리할 당시 컬렉션에서 데이터를 가져와 반환할 때 타입을 _RefFirestore로 지정해 줬었다.

그리고 화면단에서 저 컴포저블 함수를 받아서 처리를 해야 하는데

vue의 ref를 사용하지 않으면 값이 변경되어도 화면에는 렌더링이 되지 않기 때문에 제네릭이던 뭐든... 이용해 ref를 감싸줘야 했다.

타입을 지정하지 않으면 어떻게 이방법 저방법 하다 보니 값을 받을 순 있었는데 소스상에선 계속 빨간줄이 그어져 있고...뭔가 맵핑이 잘 되지도 않았고, 타입을 지정하지 않았다고 에러도 뜨고 아주 난리가 보통이 아니라 그냥 firebase 패키지만 쓰기로 마음먹었다.

 

vuefire 공식문서를 봐보면 nuxt용 vuefire 모듈이 있긴 한데, 아직 정식버전은 아닌 것 같았다.

이것도 사용해 봤는 데 사용방법 자체가 공식문서에 정보도 아직 부족하고 계속 개발중이라는 문구가 있는 걸 봐선 추후에 다시 봐봐야 할 것 같았다.

 

작업한 소스

https://github.com/smw0807/vue3/blob/main/nuxt3-quasar/pages/firebase/database.vue

 

GitHub - smw0807/vue3: vue3 관련

vue3 관련. Contribute to smw0807/vue3 development by creating an account on GitHub.

github.com

https://github.com/smw0807/vue3/blob/main/nuxt3-quasar/composables/useFirestoreDB.ts

 

GitHub - smw0807/vue3: vue3 관련

vue3 관련. Contribute to smw0807/vue3 development by creating an account on GitHub.

github.com

https://github.com/smw0807/vue3/blob/main/nuxt3-quasar/composables/useFirebase.ts

 

GitHub - smw0807/vue3: vue3 관련

vue3 관련. Contribute to smw0807/vue3 development by creating an account on GitHub.

github.com

시간날때마다 작업할 예정이라 본문의 소스와 달라질 수 있습니다.
반응형