White Box技術部

WEB開発のあれこれ(と何か)

Firebase AuthenticationとFirestoreへのTypeScriptでのアクセス方法

TypeScriptで書いているNext.jsアプリのユーザ情報管理を、 Firebase AuthenticationとFirestoreで行ったのですが、 調べたときに見かけたコードサンプルは、呼び出し方がJavaScriptのままだったので、 TypeScriptっぽいサンプルもここに残して置きます。

というかWeb v9の書き方じゃない・・・と後から気付きました・・・
https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja#web-v9

Firebase SDKの設定

TSの書き方では(もといWeb v9の書き方では)firebase.authのような呼び出し方をすることはできないので、 それぞれgetAuthやgetFirestoreを使ってインスタンスを取得する必要があります。

  • libs/firebase.ts
import * as firebase from 'firebase/app';
import { Auth, getAuth } from 'firebase/auth';
import { Firestore, getFirestore } from 'firebase/firestore';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

if (typeof window !== 'undefined' && !firebase.getApps().length) {
  firebase.initializeApp(firebaseConfig);
}

export default firebase;
export const getFirebaseAuth = (): Auth => getAuth();
export const getFirebaseDb = (): Firestore => getFirestore();

firebaseをexportしていますが、今のところこれを触ることはなかったので、いらないかもしれません。

Firebaseアクセス

次はAuthenticationとFirestoreへの接続です。

①Authenticationへのサインイン

signInWithPopupの呼び出しは、パラメータにAuthインスタンスと対象のプロバイダを渡すことで行なえます。
サインインが成功するとUserCredentialが返却されるので、ユーザ情報が必要な場合は、そのuser属性を取得してください。

import { GoogleAuthProvider, User, UserCredential, signInWithPopup } from 'firebase/auth';
import { getFirebaseAuth } from 'libs/firebase';
〜〜〜
const auth = getFirebaseAuth();
const provider = new GoogleAuthProvider();
const signInResult: UserCredential = await signInWithPopup(auth, provider);
const crrentUser: User = signInResult.user;

Auth.currentUserに関する注意

AuthインターフェイスにはcurrentUserがあるのですが、これはonAuthStateChangedの処理後でなければ値がnullになっているので、利用する場合は気を付けてください。

②Firestoreへのアクセス

FirestoreもAuthenticationと同じように、getFirestore(ここではgetFirebaseDb)を使って処理を行います。 対象のコレクションとドキュメントを指定し、collection関数とdoc関数を使って、入れ子にして呼び出します。

doc(collection(Firestoreインスタンス, コレクション名), ドキュメントID)

これで得られるDocumentDataオブジェクトに、getDocで取得、setDocで追加の操作をします。

データ取得

getDocで取得したオブジェクトのdata()を呼び出すと、コレクションの値が取得できます。
フィールドの値はDocumentData["フィールド名"]の形で取得します。

import { DocumentData, collection, doc, getDoc } from 'firebase/firestore';
〜〜〜
const db = getFirebaseDb();
const uid = "対象のドキュメントID";
const document = await getDoc(doc(collection(db, 'users'), uid));

if (document.exists()) {
  const documentId = document.id;  // この場合、uidと同じ値
  const documentData: DocumentData = document.data();
  const user: UserType = {
    id: documentId,
    name: documentData['name'],
    email: documentData['email'],
    picture: documentData['picture'],
    token: documentData['token'],
  };

データ追加

ドキュメントをコレクションに追加するには、DocumentDataにsetDocを行います。
登録するデータはオブジェクトで定義してください。

const data = {
  name: "名前",
  email: "メールアドレス",
  picture: "アバターURL",
  token: "アクセストークン",
};

await setDoc(対象のDocumentData, data);

③コード例

実際のコードだと以下のようになりました。

  • functions/account.ts
import { GoogleAuthProvider, UserCredential, signInWithPopup } from 'firebase/auth';
import { Firestore, collection, doc, getDoc, setDoc } from 'firebase/firestore';
import Router from 'next/router';

import { convertToUser } from './user';

import { getFirebaseAuth, getFirebaseDb } from 'libs/firebase';
import { UserType } from 'application';

export const googleSignin = () => {
  (async () => {
    const auth = getFirebaseAuth();
    const provider = new GoogleAuthProvider();
    try {
      const signInResult: UserCredential = await signInWithPopup(auth, provider);
      const db = getFirebaseDb();
      const exist = await existFirebaseUser(db, signInResult.user?.uid);
      if (!exist) {
        // firebaseへのユーザ登録処理
        const user: UserType = await convertToUser(signInResult.user);
        const isRegistered = await registerUser(db, user);
        if (isRegistered) {
          console.log('登録OK uid: ' + user.id);
        } else {
          console.log('登録NG uid: ' + user.id);
          Router.push('/error');
        }
      }
      Router.push('/mypage');
    } catch (error) {
      console.log(error);
      console.log(error.code);
      console.log(error.message);
    }
  })();
};

const existFirebaseUser = async (db: Firestore, uid: string): Promise<boolean> => {
  const result = (await getDoc(doc(collection(db, 'users'), uid))).exists();
  return result;
};

const registerUser = async (db: Firestore, user: UserType): Promise<boolean> => {
  const userData = {
    name: user.name,
    email: user.email,
    picture: user.picture,
    token: user.token,
  };
  try {
    await setDoc(doc(collection(db, 'users'), user.id), userData);
    console.log('登録成功');
    return true;
  } catch (error) {
    console.log('登録失敗:' + error);
    return false;
  }
};

export const signout = () => {
  getFirebaseAuth()
    .signOut()
    .then(() => {
      window.location.reload();
    });
};
  • functions/user.ts
import { User } from 'firebase/auth';

import { UserType } from 'application';

export const convertToUser = async (firebaseUser: User): Promise<UserType> => {
  if (firebaseUser) {
    const user: UserType = {
      id: firebaseUser.uid,
      name: firebaseUser.displayName,
      email: firebaseUser.email,
      picture: firebaseUser.photoURL,
      token: await firebaseUser.getIdToken(),
    };
    return user;
  }
  return null;
};