🦉

Nuxt.js のアプリから Firebase Storage へ画像をアップロードする方法

2022/04/16に公開約6,600字

Firebase の Storage を利用すれば無料でクラウドのファイルサーバーが利用できます。

無料枠だと容量が少ないという問題はあるものの小規模アプリにはいいかもしれないですね。

ちなみに無料枠での上限は以下のようになっています。

  • GB 保存済み:5 GB
  • GB ダウンロード済み:1 GB/日
  • オペレーションをアップロード:2 万/日
  • ダウンロード オペレーション:5 万/日

今回は Nuxt.js のアプリに Firebase ライブラリを利用して画像ファイルをアップロードをやってみます。

全体の流れとしては以下の通りです。

  1. Firebase ライブラリのインストール
  2. Firebase Storage の設定
  3. Nuxt 側の設定
  4. アップロード処理作成
  5. 画像の表示(ダウンロード)処理作成

Firebase ライブラリインストール

npm のコマンドを実行。

npm install --save firebase

plugins/firebase.js という設定ファイルを作成し反映します。

import firebase from 'firebase/app'
import 'firebase/app'
import 'firebase/firestore'
import 'firebase/storage'

const config = {
  projectId: process.env.FIREBASE_PROJECT_ID,
  storageBucket: process.env.FIREBASE_STORAGEBUCKET,
}

if (!firebase.apps.length) {
  firebase.initializeApp(config)
}

export default firebase

環境変数(process.env.FIREBASE_PROJECT_IDprocess.env.FIREBASE_STORAGEBUCKET)に関しては @nuxtjs/dotenv を利用してそれぞれの .env の設定値を参照できます。Firestore 使う場合はここでインポートしてくおけばOKです。

Firebase Storage の設定

Firebase の Storage から新しく作成する。作成後に連携するための URL が発行されるのでそれをコピーして .env に設定。

Nuxt 側の設定

.env にはプロジェクトID も設定。

FIREBASE_PROJECT_ID='your-project-name'
FIREBASE_STORAGEBUCKET='gs://abcdefg-hijklmn.com'

アップロード処理作成

pages フォルダの中にフォームを作成するための vue ファイルを作成しフォームを作成。

今回は Vuefy を利用しタイトルとメモも合わせて Firestore に登録するようにする。

<h2 class="title is-3 has-text-grey">新規投稿</h2>
<b-field label="タイトル">
  <b-input v-model="title" required></b-input>
</b-field>
<b-field label="メモ">
  <b-input rows="2" maxlength="100" type="textarea" v-model="memo"></b-input>
</b-field>

<b-field>
  <b-upload v-model="file" drag-drop required>
    <section class="section">
      <div class="content has-text-centered">
        <p>
          <b-icon icon="upload" size="is-large"> </b-icon>
        </p>
        <p>画像ファイルをドロップしてください。</p>
      </div>
    </section>
  </b-upload>
</b-field>
<div class="tags">
  <span class="tag is-primary" v-if="file">
    {{ file.name }}
    <button class="delete" type="button"
      @click="this.file = null"
    ></button>
  </span>
</div>
<b-button type="is-primary" outlined @click="create()">送信</b-button>
export default {
  data() {
    return {
      title: "",
      memo: "",
      file: null,
      type: "",
      dataUrl: "",
    };
  },
  methods: {
    create() {
      this.$store.dispatch("items/create", {
        title: this.title,
        memo: this.memo,
        file: { name: this.fileName, type: this.type, dataUrl: this.dataUrl },
      });
    },
  },
  watch: {
    file(val) {
      this.fileName = val.name;
      if (val.type == "image/jpeg") this.type = "jpeg";
      else if (val.type == "image/png") this.type = "png";
      else if (val.type == "image/gif") this.type = "gif";
      const reader = new FileReader();
      reader.readAsDataURL(this.file);
      reader.onload = (e) => {
        this.dataUrl = e.target.result;
      };
    },
  },
};

dispatch を使うことで store の非同期関数を呼ぶことができます。watch(監視プロパティ)では画像がセットされてた時点で画像データを取得するような仕組みです。

次にその store の処理です。store/items.js を作成し登録時の actoins を作成。vuexfire を使っているので firestoreAction を利用した書き方です。

import firebase from '~/plugins/firebase'
import { firestoreAction } from 'vuexfire'

const db = firebase.firestore()
const itemsRef = db.collection('items')

export const actions = {
  create: firestoreAction((context, input) => {
   // Storage にアップロード
   const storageRef = firebase.storage().ref();
   const fileUrl = `pictures/${input.file.name}.${input.file.type}`;
   const picturesRef = storageRef.child(fileUrl);
   picturesRef.putString(input.file.dataUrl, 'data_url').then(() => {
     // Firestore に保存
      itemsRef.add({
        title: input.title,
        memo: input.memo,
        fileUrl: fileUrl,
        created: firebase.firestore.FieldValue.serverTimestamp()
      }).then(() => {
       $nuxt.$router.push('/')
     });
    });
  }),

pictures は今回は写真を投稿するためのフォルダ名です。ここで指定すると Storage の方でフォルダ構成にして保存できます。

画像をセットして送信すればアップロードできるはずです。Firebase Storage の画面を開くと画像を確認できます。

画像の表示(ダウンロード)処理作成

次に画像を Nuxt 側で取得して表示してみます。まずはページの vue ファイルを作成。

<div class="columns is-mobile">
  <div class="card" v-for="item in items" :key="item.id">
    <header class="card-header">
      <p class="card-header-title has-text-grey">
        {{ item.title }}
      </p>
    </header>
    <div class="card-content">
      <img :src="setImageUrl(item.fileUrl)">
    </div>
    <footer class="card-footer">
      <div class="card-footer-item">
        <span>{{ item.memo }}</span>
      </div>
    </footer>
  </div>
</div>

items などをスクリプト側で用意し表示する画像の URL はストレージのリファレンスから getDownloadURL() を使って取得できます。

import firebase from '~/plugins/firebase'

export default {
  data() {
    return {
      items: [],
      images: [],
    }
  },
  async created() {
    // 投稿の取得
    this.$store.dispatch("items/init");
    this.items = this.$store.getters['items/dbItems']
    // 画像
    const storageRef = firebase.storage().ref().child('pictures');
    const list = await storageRef.listAll();
    list.items.forEach(item => {
      item.getDownloadURL().then(url => {
        this.images.push({path: item.fullPath, url: url});
      })
    })
  },
  methods: {
    setImageUrl(url) {
      const index = this.images.findIndex(image => image.path == url)
      if (index >= 0) {
        return this.images[index].url;
      }
    }
  }
};

投稿データは dispatch でストアデータを DB と紐付けし gettersstore からデータを取得。DBから取得する際 orderBy を使えばソートも可能です。

import firebase from '~/plugins/firebase'
import { firestoreAction } from 'vuexfire'

const db = firebase.firestore()
const itemsRef = db.collection('items')

export const state = () => ({
  items: []
})

export const getters = {
  dbItems(state) {
    return state.items;
  },
}

export const actions = {
  init: firestoreAction(({ bindFirestoreRef }) => {
		bindFirestoreRef('items', itemsRef.orderBy('created', 'desc'));
  }),

これで Store の画像を表示できるはずです。

まとめ

Nuxt.js で Firebase Storage へ画像アップロードする方法について解説しました。

Firebase は無料枠もあるので小規模アプリの開発などで使えそうですね。

今回の記事がアプリ開発のお役に立てれば幸いです。

参考

Qiita Firebase CloudStorageを使ってブログのサムネイルは画像を設定する【Vue.js】
Firebase Storage 公式ドキュメント

Discussion

ログインするとコメントできます