Nuxt.jsでfirestoreのデータ取得をSSRで行うには?


目的

nuxt.jsでfirestoreのデータを取得するときに困ったことが起きました。それは……
セキュリティルールが設けられているfirestoreのデータをサーバーサイドで取得する時どうすんの?って話です。
セキュリティルールとはfirestoreからデータを取得する際、条件付きでデータを取得することができるユーザーを限定できるようにするためのルールです。
今回扱うコレクションはusersで下のようにユーザーIDを持つユーザのみアクセスできるようにしています。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{id} {
    	allow read, write: if request.auth.uid != null;
    }
  }
}

セキュリティルールを設けていない、またはクライアント側でデータの取得を行う場合は以下のURLのようにfirebaseモジュールを初期化すればデータを取得することができる。
Firebase を JavaScript プロジェクトに追加する
Cloud Firestore でデータを取得する  |  Firebase

しかし、サーバー側でデータの取得を行おうとすると

 ERROR  Missing or insufficient permissions.                                                                             14:33:51

  at new FirestoreError (node_modules\@firebase\firestore\src\util\error.ts:166:5)
  at JsonProtoSerializer.fromRpcStatus (node_modules\@firebase\firestore\src\remote\serializer.ts:130:12)
  at JsonProtoSerializer.fromWatchChange (node_modules\@firebase\firestore\src\remote\serializer.ts:433:40)
  at PersistentListenStream.onMessage (node_modules\@firebase\firestore\src\remote\persistent_stream.ts:568:41)
  at node_modules\@firebase\firestore\src\remote\persistent_stream.ts:448:21
  at node_modules\@firebase\firestore\src\remote\persistent_stream.ts:501:18
  at node_modules\@firebase\firestore\src\util\async_queue.ts:285:14
  at processTicksAndRejections (internal/process/task_queues.js:93:5)

上記のエラーが発生してしまう。
今回はこの問題を解決します。


環境

OS: Windows10 home
エディタ: Visual Studio Code


ディレクトリ構成

fadmin_test(任意のプロジェクトの名前)/
     ├ modules/  <= デフォルトでは存在しないので新たに追加してください
     │      └ get_data.ts
     │
     ├ pages/
     │      └ index.ts
     │
     └ utils/  <= デフォルトでは存在しないので新たに追加してください
             ├ path/
             │   └ serviceAccountKey.json
             └ firebaseAdmin.ts

※今回使用するファイルのみ表示しています。


解決方法

結論から言うとfirebase-adminを使えば良いということになります。
firebase-adminを追加したいディレクトリに移動し下記のコマンドを実行する

C:\fadmin_test> npm install firebase-admin

次にこの状態でfirebaseサービスにアクセスするためには秘密鍵というjson形式のファイルが必要なのでそれを取得します。
まずはfirebaseのコンソール画面に行きます。
下画像からプロジェクトを設定をクリック

f:id:fetchkun:20200518175237p:plain
下画像のサービスアカウントを選択

f:id:fetchkun:20200518175250p:plain
下画像の新しい秘密鍵の生成をクリック後"公開リポジトリには保存するな"の注意が表示されキーを生成をクリックしダウンロードします。

f:id:fetchkun:20200518175304p:plain
ダウンロードしたjsonファイルはプロジェクト(ここではfadmin_test)のutils/path内に保存し、ファイル名をserviceAccountKey.jsonに変えます

次にfirebase-adminを初期化します。


utils/firebaseAdmin.ts

let admin

if (process.server) {
  admin = require('firebase-admin')
  if (!admin.apps.length) {
    const serviceAccount = require('./path/serviceAccountKey.json')  // ①
    admin.initializeApp({
      credential: admin.credential.cert(serviceAccount),  // ②
      databaseURL: 'データベースのURL'  // ③
    })
  }
}

export {
  admin
}

・①:ここで生成した秘密鍵を指定し、②で認証に使います。
・③:ここには通常のデータベースを使用する際に扱うdatabaseURLを記入します。(下画像の緑マーカー部分)

f:id:fetchkun:20200518175120p:plain

次にfirestoreからデータを取得するためのファイルを作ります。firebaseモジュールをimportする場合とやり方は変わりません。


modules/get_data.ts

import * as fadmin from '../utils/firebaseAdmin'

const get_user = async () => {
  return new Promise(callback => {
    fadmin.admin.firestore().collection('users').get()
      .then((query: any) => {
        const items: any[] = []
        query.forEach((doc: any) => {
          const item = {
            id: doc.id,
            data: doc.data()
          }
          items.push(item)
        })
        console.log(items)  // <= この部分がターミナル上に表示される
        callback(items)
      })
  })
}

export {
  get_user
}

次に画面に表示されるpagesフォルダのindex.vueからget_data.tsを呼び出しデータを取ってきます。


modules/get_data.ts

<template>
  <section>
    pages.index
    <div>
      {{ view }}
    </div>
  </section>
</template>

<script lang="ts">
import Vue from 'vue'
import { get_user } from '../modules/get_data'

export default Vue.extend({
  async asyncData ({ params }) {  // ①
    return {
      view: await get_user()
    }
  }
})
</script>

・①:サーバーサイドでデータを取得するのが今回の目的なのでasyncDataを用います。

最後以下のコードを実行する

C:\fadmin_test> npm run dev

するとターミナル上に取得したデータが表示される

~~省略~~
[                            
  {                          
    id: 'a',                 
    data: {                  
      email: 'b@gmail.com'   
    }                        
  },                         
  {                          
    id: 'c',                 
    data: {                  
      email: 'd@gmail.com'   
    }                        
  }                          
]                                                     

またindex.vueのasyncData内で取得したデータを受け取っているため
http://localhost:3000/にアクセスすると下画像のように取得したデータが表示される

f:id:fetchkun:20200518175608p:plain


まとめ

今回はサーバーサイドでfirebaseのデータを取得する方法をまとめました。
ここではfirebase-adminを使った方法でサーバーサイドからデータを取得しました。
秘密鍵を生成しないといけないなどちょっと面倒なのでもっと良い方法があるとおもうんですけどね~
ここまで読んでくれてありがとう