Open29

Firebase調査

西川信行西川信行

ドキュメントの設定

import { doc, setDoc } from "firebase/firestore";

// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "LA"), {
  name: "Los Angeles",
  state: "CA",
  country: "USA"
});

マージの挙動

import { doc, setDoc } from "firebase/firestore";

const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
西川信行西川信行
type User = {
    displayName: string
    photoURL: string
    createdAt: firebase.firestore.Timestamp
    updatedAt: firebase.firestore.Timestamp
  }
西川信行西川信行

ドキュメントの作成

type User = {
  displayName: string
  photoURL: string
  createdAt: firebase.firestore.Timestamp
  updatedAt: firebase.firestore.Timestamp
}
async createdUser(user: User): Promise<DocumentReference> {
  return firebase.firestore().collection('users').add(user)
}

西川信行西川信行

ドキュメントリファレンスの取得

dbfirebase.firestoreだと思って大丈夫。

const alovelaceDocumentRef = db.collection('users').doc('alovelace');

import { doc } from "firebase/firestore";

const alovelaceDocumentRef = doc(db, 'users', 'alovelace');

firebase/firestoreからdocをインポートし、そのdocを使用することでドキュメントリファレンスを作成する。docは第一引数にdb、第二引数にコレクション名、第三引数にドキュメント名を指定します。

docの第三引数を空っぽにすると、ドキュメントIDが自動採番されるのかな?

西川信行西川信行

コレクションリファレンスの取得。

const usersCollectionRef = db.collection('users')

import { collection } from "firebase/firestore";

const usersCollectionRef = collection(db, 'users');

コレクションリファレンスを作成するときは、firebase/firestoreからcollectionをインポートして使用する。collectionの第一引数にdbを指定し、第二引数にコレクション名を指定する。

西川信行西川信行

ドキュメントリファレンスの取得の省略形

const alovelaceDocumentRef = db.doc('users/alovelace');

import { doc } from "firebase/firestore";

const alovelaceDocumentRef = doc(db, 'users/alovelace');

ドキュメントリファレンスを、省略して取得することも可能。docの第二引数にコレクション名/ドキュメントIDを指定することで取得可能。

西川信行西川信行

サブコレクションのドキュメントリファレンスの取得

const messageRef = db.collection('rooms').doc('roomA')
                .collection('messages').doc('message1');

import { doc } from "firebase/firestore";

const messageRef = doc(db, "rooms", "roomA", "messages", "message1");

docは第二引数から、コレクション→ドキュメント→コレクション→ドキュメントの繰り返し。

西川信行西川信行

citiesコレクションの中に、LAというドキュメントIDのドキュメントを作成し、ドキュメントの作成が完了すれば、コンソールにメッセージを表示する。

// Add a new document in collection "cities"
db.collection("cities").doc("LA").set({
    name: "Los Angeles",
    state: "CA",
    country: "USA"
})
.then(() => {
    console.log("Document successfully written!");
})
.catch((error) => {
    console.error("Error writing document: ", error);
});

import { doc, setDoc } from "firebase/firestore";

// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "LA"), {
  name: "Los Angeles",
  state: "CA",
  country: "USA"
});

console.log("Document successfully written!");

docはドキュメントリファレンスを取得するだけ。そのドキュメントリファレンスを用いてドキュメントの生成を行うのはsetDocを使用する。setDocは第一引数にドキュメントリファレンスを持ち、第二引数にデータそのものを記述する。この第二引数は、旧バージョンの.setの引数と一致する。

西川信行西川信行

ドキュメントリファレンスに.setを用いて、データを更新する方法。marge : trueを使用する。

基本的には、この方法を使用するよりもupdateを使用した方が良い(createとupdateのセキュリティルールが同一になるため、ドキュメントの作成時刻を持つデータモデルを扱う場合に安全性を確保できないため。)

データが存在する場合はupdate、存在しなければcreateというユースケースで利用する場合はあるかも。

const cityRef = db.collection('cities').doc('BJ');

const setWithMerge = cityRef.set({
    capital: true
}, { merge: true });

import { doc, setDoc } from "firebase/firestore";

const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });

setDocの第三引数にmerge : trueを与える。

西川信行西川信行

データ型

Firestoreでは、文字列・ブール値・日付・NULL・ネストされた文字列・オブジェクトなどのデータ型をオブジェクトに書き込むことができる。Cloud Firestoreは、数値を常にdouble型として保存する。

var docData = {
    stringExample: "Hello world!",
    booleanExample: true,
    numberExample: 3.14159265,
    dateExample: firebase.firestore.Timestamp.fromDate(new Date("December 10, 1815")),
    arrayExample: [5, true, "hello"],
    nullExample: null,
    objectExample: {
        a: 5,
        b: {
            nested: "foo"
        }
    }
};
db.collection("data").doc("one").set(docData).then(() => {
    console.log("Document successfully written!");
});

import { doc, setDoc, Timestamp } from "firebase/firestore";

const docData = {
    stringExample: "Hello world!",
    booleanExample: true,
    numberExample: 3.14159265,
    dateExample: Timestamp.fromDate(new Date("December 10, 1815")),
    arrayExample: [5, true, "hello"],
    nullExample: null,
    objectExample: {
        a: 5,
        b: {
            nested: "foo"
        }
    }
};
await setDoc(doc(db, "data", "one"), docData);

新バージョンでは、TimestampTimestampとしてインポートする必要がある。

西川信行西川信行

ドキュメントの追加

こんな風に書いてありました。

set() を使用してドキュメントを作成する場合、作成するドキュメントの ID を指定する必要があります。次に例を示します。

一応、docの引数を空っぽにしてsetを行うと、ドキュメントのIDを自動採番してドキュメントを作成することが可能になります。しかし、ドキュメントのIDを自動採番するなら、コレクションに対してaddを使用した方が良いかもです。

ドキュメントIDを指定してドキュメントを作成するパターン。

db.collection("cities").doc("new-city-id").set(data);

import { doc, setDoc } from "firebase/firestore";

await setDoc(doc(db, "cities", "new-city-id"), data);

自動採番するパターン(add)を使用。

// Add a new document with a generated id.
db.collection("cities").add({
    name: "Tokyo",
    country: "Japan"
})
.then((docRef) => {
    console.log("Document written with ID: ", docRef.id);
})
.catch((error) => {
    console.error("Error adding document: ", error);
});

import { collection, addDoc } from "firebase/firestore";

// Add a new document with a generated id.
const docRef = await addDoc(collection(db, "cities"), {
  name: "Tokyo",
  country: "Japan"
});
console.log("Document written with ID: ", docRef.id);
西川信行西川信行

自動生成されたIDを持つドキュメントリファレンスを作成して、後でそのリファレンスを使用する場合。

// Add a new document with a generated id.
const newCityRef = db.collection("cities").doc();

// later...
newCityRef.set(data);

import { collection, doc, setDoc } from "firebase/firestore";

// Add a new document with a generated id
const newCityRef = doc(collection(db, "cities"));

// later...
await setDoc(newCityRef, data);

新の書き方が結構特殊。collectionを用いてコレクションリファレンスを作成し、それをdocの引数に渡すことで自動採番されたドキュメントリファレンスを作成する。

西川信行西川信行

ドキュメントの更新。

var washingtonRef = db.collection("cities").doc("DC");

// Set the "capital" field of the city 'DC'
return washingtonRef.update({
    capital: true
})
.then(() => {
    console.log("Document successfully updated!");
})
.catch((error) => {
    // The document probably doesn't exist.
    console.error("Error updating document: ", error);
});

import { doc, updateDoc } from "firebase/firestore";

const washingtonRef = doc(db, "cities", "DC");

// Set the "capital" field of the city 'DC'
await updateDoc(washingtonRef, {
  capital: true
});

docはいつも通り、第一引数がdbであり、第二引数以降はコレクション→ドキュメント→コレクション→ドキュメントと続いていく。指定したドキュメントリファレンスに対して、updateDocを使用すればOK。

西川信行西川信行

サーバータイムスタンプ

var docRef = db.collection('objects').doc('some-id');

// Update the timestamp field with the value from the server
var updateTimestamp = docRef.update({
    timestamp: firebase.firestore.FieldValue.serverTimestamp()
});

import { updateDoc, serverTimestamp } from "firebase/firestore";

const docRef = doc(db, 'objects', 'some-id');

// Update the timestamp field with the value from the server
const updateTimestamp = await updateDoc(docRef, {
    timestamp: serverTimestamp()
});

なにはなくともドキュメントリファレンスの取得が大事。

後は、updateDocを用いてドキュメントを更新すればOK。その際、FieldValueのserverTimestampではなく、serverTimestampを使用すればOK。

西川信行西川信行

ネストされたオブジェクトのフィールドを更新する。

// Create an initial document to update.
var frankDocRef = db.collection("users").doc("frank");
frankDocRef.set({
    name: "Frank",
    favorites: { food: "Pizza", color: "Blue", subject: "recess" },
    age: 12
});

// To update age and favorite color:
db.collection("users").doc("frank").update({
    "age": 13,
    "favorites.color": "Red"
})
.then(() => {
    console.log("Document successfully updated!");
});

import { doc, setDoc, updateDoc } from "firebase/firestore";

// Create an initial document to update.
const frankDocRef = doc(db, "users", "frank");
await setDoc(frankDocRef, {
    name: "Frank",
    favorites: { food: "Pizza", color: "Blue", subject: "recess" },
    age: 12
});

// To update age and favorite color:
await updateDoc(frankDocRef, {
    "age": 13,
    "favorites.color": "Red"
});
西川信行西川信行

配列内の要素を更新する。

var washingtonRef = db.collection("cities").doc("DC");

// Atomically add a new region to the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});

// Atomically remove a region from the "regions" array field.
washingtonRef.update({
    regions: firebase.firestore.FieldValue.arrayRemove("east_coast")
});

import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";

const washingtonRef = doc(db, "cities", "DC");

// Atomically add a new region to the "regions" array field.
await updateDoc(washingtonRef, {
    regions: arrayUnion("greater_virginia")
});

// Atomically remove a region from the "regions" array field.
await updateDoc(washingtonRef, {
    regions: arrayRemove("east_coast")
});
西川信行西川信行

数値を増やす

var washingtonRef = db.collection('cities').doc('DC');

// Atomically increment the population of the city by 50.
washingtonRef.update({
    population: firebase.firestore.FieldValue.increment(50)
});

import { doc, updateDoc, increment } from "firebase/firestore";

const washingtonRef = doc(db, "cities", "DC");

// Atomically increment the population of the city by 50.
await updateDoc(washingtonRef, {
    population: increment(50)
});
西川信行西川信行

トランザクションを作成して実行

// Create a reference to the SF doc.
const sfDocRef = db.collection("cities").doc("SF");

// Uncomment to initialize the doc.
// sfDocRef.set({ population: 0 });

return db.runTransaction((transaction) => {
    // This code may get re-run multiple times if there are conflicts.
    return transaction.get(sfDocRef).then((sfDoc) => {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        // Add one person to the city population.
        // Note: this could be done without a transaction
        //       by updating the population using FieldValue.increment()
        var newPopulation = sfDoc.data().population + 1;
        transaction.update(sfDocRef, { population: newPopulation });
    });
}).then(() => {
    console.log("Transaction successfully committed!");
}).catch((error) => {
    console.log("Transaction failed: ", error);
});

import { runTransaction } from "firebase/firestore";

try {
  await runTransaction(db, async (transaction) => {
    const sfDoc = await transaction.get(sfDocRef);
    if (!sfDoc.exists()) {
      throw "Document does not exist!";
    }

    const newPopulation = sfDoc.data().population + 1;
    transaction.update(sfDocRef, { population: newPopulation });
  });
  console.log("Transaction successfully committed!");
} catch (e) {
  console.log("Transaction failed: ", e);
}
西川信行西川信行

トランザクションから情報を渡す

// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

db.runTransaction((transaction) => {
    return transaction.get(sfDocRef).then((sfDoc) => {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        var newPopulation = sfDoc.data().population + 1;
        if (newPopulation <= 1000000) {
            transaction.update(sfDocRef, { population: newPopulation });
            return newPopulation;
        } else {
            return Promise.reject("Sorry! Population is too big.");
        }
    });
}).then((newPopulation) => {
    console.log("Population increased to ", newPopulation);
}).catch((err) => {
    // This will be an "population is too big" error.
    console.error(err);
});

import { doc, runTransaction } from "firebase/firestore";

// Create a reference to the SF doc.
const sfDocRef = doc(db, "cities", "SF");

try {
  const newPopulation = await runTransaction(db, async (transaction) => {
    const sfDoc = await transaction.get(sfDocRef);
    if (!sfDoc.exists()) {
      throw "Document does not exist!";
    }

    const newPop = sfDoc.data().population + 1;
    if (newPop <= 1000000) {
      transaction.update(sfDocRef, { population: newPop });
      return newPop;
    } else {
      return Promise.reject("Sorry! Population is too big");
    }
  });

  console.log("Population increased to ", newPopulation);
} catch (e) {
  // This will be a "population is too big" error.
  console.error(e);
}
西川信行西川信行

バッチ書き込み

// Get a new write batch
var batch = db.batch();

// Set the value of 'NYC'
var nycRef = db.collection("cities").doc("NYC");
batch.set(nycRef, {name: "New York City"});

// Update the population of 'SF'
var sfRef = db.collection("cities").doc("SF");
batch.update(sfRef, {"population": 1000000});

// Delete the city 'LA'
var laRef = db.collection("cities").doc("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().then(() => {
    // ...
});

import { writeBatch, doc } from "firebase/firestore";

// Get a new write batch
const batch = writeBatch(db);

// Set the value of 'NYC'
const nycRef = doc(db, "cities", "NYC");
batch.set(nycRef, {name: "New York City"});

// Update the population of 'SF'
const sfRef = doc(db, "cities", "SF");
batch.update(sfRef, {"population": 1000000});

// Delete the city 'LA'
const laRef = doc(db, "cities", "LA");
batch.delete(laRef);

// Commit the batch
await batch.commit();
西川信行西川信行

ドキュメントを削除する。

db.collection("cities").doc("DC").delete().then(() => {
    console.log("Document successfully deleted!");
}).catch((error) => {
    console.error("Error removing document: ", error);
});

import { doc, deleteDoc } from "firebase/firestore";

await deleteDoc(doc(db, "cities", "DC"));

警告: ドキュメントを削除しても、そのドキュメントのサブコレクションは削除されません。

西川信行西川信行

フィールドを削除する

var cityRef = db.collection('cities').doc('BJ');

// Remove the 'capital' field from the document
var removeCapital = cityRef.update({
    capital: firebase.firestore.FieldValue.delete()
});

import { doc, updateDoc, deleteField } from "firebase/firestore";

const cityRef = doc(db, 'cities', 'BJ');

// Remove the 'capital' field from the document
await updateDoc(cityRef, {
    capital: deleteField()
});
西川信行西川信行

データの例

var citiesRef = db.collection("cities");

citiesRef.doc("SF").set({
    name: "San Francisco", state: "CA", country: "USA",
    capital: false, population: 860000,
    regions: ["west_coast", "norcal"] });
citiesRef.doc("LA").set({
    name: "Los Angeles", state: "CA", country: "USA",
    capital: false, population: 3900000,
    regions: ["west_coast", "socal"] });
citiesRef.doc("DC").set({
    name: "Washington, D.C.", state: null, country: "USA",
    capital: true, population: 680000,
    regions: ["east_coast"] });
citiesRef.doc("TOK").set({
    name: "Tokyo", state: null, country: "Japan",
    capital: true, population: 9000000,
    regions: ["kanto", "honshu"] });
citiesRef.doc("BJ").set({
    name: "Beijing", state: null, country: "China",
    capital: true, population: 21500000,
    regions: ["jingjinji", "hebei"] });

import { collection, doc, setDoc } from "firebase/firestore";

const citiesRef = collection(db, "cities");

await setDoc(doc(citiesRef, "SF"), {
    name: "San Francisco", state: "CA", country: "USA",
    capital: false, population: 860000,
    regions: ["west_coast", "norcal"] });
await setDoc(doc(citiesRef, "LA"), {
    name: "Los Angeles", state: "CA", country: "USA",
    capital: false, population: 3900000,
    regions: ["west_coast", "socal"] });
await setDoc(doc(citiesRef, "DC"), {
    name: "Washington, D.C.", state: null, country: "USA",
    capital: true, population: 680000,
    regions: ["east_coast"] });
await setDoc(doc(citiesRef, "TOK"), {
    name: "Tokyo", state: null, country: "Japan",
    capital: true, population: 9000000,
    regions: ["kanto", "honshu"] });
await setDoc(doc(citiesRef, "BJ"), {
    name: "Beijing", state: null, country: "China",
    capital: true, population: 21500000,
    regions: ["jingjinji", "hebei"] });
``
西川信行西川信行

ドキュメントを取得する

var docRef = db.collection("cities").doc("SF");

docRef.get().then((doc) => {
    if (doc.exists) {
        console.log("Document data:", doc.data());
    } else {
        // doc.data() will be undefined in this case
        console.log("No such document!");
    }
}).catch((error) => {
    console.log("Error getting document:", error);
});

import { doc, getDoc } from "firebase/firestore";

const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);

if (docSnap.exists()) {
  console.log("Document data:", docSnap.data());
} else {
  // doc.data() will be undefined in this case
  console.log("No such document!");
}
西川信行西川信行

ドキュメントを取得する

var docRef = db.collection("cities").doc("SF");

// Valid options for source are 'server', 'cache', or
// 'default'. See https://firebase.google.com/docs/reference/js/firebase.firestore.GetOptions
// for more information.
var getOptions = {
    source: 'cache'
};

// Get a document, forcing the SDK to fetch from the offline cache.
docRef.get(getOptions).then((doc) => {
    // Document was found in the cache. If no cached document exists,
    // an error will be returned to the 'catch' block below.
    console.log("Cached document data:", doc.data());
}).catch((error) => {
    console.log("Error getting cached document:", error);
});

import { doc, getDocFromCache } from "firebase/firestore";

const docRef = doc(db, "cities", "SF");

// Get a document, forcing the SDK to fetch from the offline cache.
try {
  const doc = await getDocFromCache(docRef);

  // Document was found in the cache. If no cached document exists,
  // an error will be returned to the 'catch' block below.
  console.log("Cached document data:", doc.data());
} catch (e) {
  console.log("Error getting cached document:", e);
}
西川信行西川信行

コレクションから複数のドキュメントを取得する

db.collection("cities").where("capital", "==", true)
    .get()
    .then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
            // doc.data() is never undefined for query doc snapshots
            console.log(doc.id, " => ", doc.data());
        });
    })
    .catch((error) => {
        console.log("Error getting documents: ", error);
    });

import { collection, query, where, getDocs } from "firebase/firestore";

const q = query(collection(db, "cities"), where("capital", "==", true));

const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});
西川信行西川信行

コレクションのすべてのドキュメントを取得する

db.collection("cities").get().then((querySnapshot) => {
    querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        console.log(doc.id, " => ", doc.data());
    });
});

import { collection, getDocs } from "firebase/firestore";

const querySnapshot = await getDocs(collection(db, "cities"));
querySnapshot.forEach((doc) => {
  // doc.data() is never undefined for query doc snapshots
  console.log(doc.id, " => ", doc.data());
});
西川信行西川信行

リアルタイムリスナー

db.collection("cities").doc("SF")
    .onSnapshot((doc) => {
        console.log("Current data: ", doc.data());
    });

import { doc, onSnapshot } from "firebase/firestore";

const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
    console.log("Current data: ", doc.data());
});
西川信行西川信行

ローカル変更のイベント

db.collection("cities").doc("SF")
    .onSnapshot((doc) => {
        var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
        console.log(source, " data: ", doc.data());
    });

import { doc, onSnapshot } from "firebase/firestore";

const unsub = onSnapshot(doc(db, "cities", "SF"), (doc) => {
  const source = doc.metadata.hasPendingWrites ? "Local" : "Server";
  console.log(source, " data: ", doc.data());
});