Cloud Firestore SDKを利用して、Firestoreのデータ操作方法を確認します。「ドキュメントの追加, 更新, 取得, 削除」「ページング処理」「トランザクション」「一括書き込み」といった操作方法を取り上げます。
なお、データベースの作成などは下記ページで実行済みです。
ドキュメントを追加
新規作成( 自動的にID生成 )
- CollectionReference.add(docData)
CollectionReferenceのaddメソッド
を利用してドキュメントを追加します。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const userRef = await db.collection('users').add({
name: {
first: 'tarou',
last: 'yamada',
},
score: 80,
birthday: firebase.firestore.Timestamp.fromDate(new Date(1980, 10, 15)),
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
})
const userDoc = await userRef.get()
console.log(userDoc.data())
// 出力例
// { birthday: Timestamp { seconds: 343062000, nanoseconds: 0 },
// createdAt: Timestamp { seconds: 1571747519, nanoseconds: 521000000 },
// name: { first: 'tarou', last: 'yamada' },
// score: 80,
// updatedAt: Timestamp { seconds: 1571747519, nanoseconds: 521000000 } }
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
以下のように、ドキュメントIDが自動採番されました。
新規作成( 自動的にID生成 )
- CollectionReference.doc()
- DocumentReference.set(docData)
DocumentReferenceのsetメソッド
を利用してドキュメントを追加することもできます。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const userRef = db.collection('users').doc()
await userRef.set({
name: {
first: 'tarou',
last: 'yamada',
},
score: 80,
birthday: firebase.firestore.Timestamp.fromDate(new Date(1980, 10, 15)),
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
})
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
新規作成( ID指定 )
- CollectionReference.doc(docId)
- DocumentReference.set(docData)
CollectionReferenceのdocメソッド
の引数で、ドキュメントIDを指定することもできます。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const userRef = db.collection('users').doc('abcdefg')
await userRef.set({
name1: 'xxxxx',
name2: 'yyyyy',
})
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
以下のように指定したドキュメントIDが設定されました。
なお、すでに指定ドキュメントが存在する場合の挙動は setメソッド
の第2引数で調整できます。
// set()に渡したデータのみを更新する
// → name2は残ったままで、name1のみが更新される
await userRef.set({ name1: 'abc' }, { merge: true })
// ドキュメント内の全てのデータが新しいデータで上書きされる
// → name2が無くなる
await userRef.set({ name1: 'abc' })
await userRef.set({ name1: 'abc' }, { merge: false })
ドキュメントを更新
下記ドキュメントを更新してみます。
更新
- CollectionReference.doc(docId)
- DocumentReference.update(docData)
DocumentReferenceのupdateメソッド
を利用してドキュメント更新します。
const userRef = db.collection('users').doc('4eADonIyVHL8bdISoN5T')
await userRef.update({
score: 80,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
})
以下のように更新されました。
もし、ドキュメントに存在しないデータを指定した場合、データが追記されます。
mapデータの更新
( ドット表記を使用する )
mapデータの一部データを更新する場合、ドット表記
を利用します。
const userRef = db.collection('users').doc('4eADonIyVHL8bdISoN5T')
await userRef.update({
'name.last': 'suzuki',
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
})
以下のように、name.last
が更新されました。
mapデータの更新
( ドット表記を使用しない )
ドット表記を使用しない場合、mapデータの全データが更新されます。
const userRef = db.collection('users').doc('4eADonIyVHL8bdISoN5T')
await userRef.update({
name: {
last: 'tanaka',
},
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
})
name.first
の指定がないため、以下のように name.last
だけになりました。
ドキュメントを取得
前準備
前準備として以下処理を実行しておきます。
await db.collection('posts').add({ title: 'title1', body: 'body1', likesCount: 50, category: 'life' })
await db.collection('posts').add({ title: 'title2', body: 'body2', likesCount: 20, category: 'news' })
await db.collection('posts').add({ title: 'title3', body: 'body3', likesCount: 40, category: 'news' })
await db.collection('posts').add({ title: 'title4', body: 'body4', likesCount: 28, category: 'life' })
await db.collection('posts').add({ title: 'title5', body: 'body5', likesCount: 39, category: 'music' })
await db.collection('posts').add({ title: 'title6', body: 'body6', likesCount: 42, category: 'science' })
await db.collection('posts').add({ title: 'title7', body: 'body7', likesCount: 29, category: 'science' })
await db.collection('posts').add({ title: 'title8', body: 'body8', likesCount: 47, category: 'life' })
await db.collection('posts').add({ title: 'title9', body: 'body9', likesCount: 36, category: 'news' })
await db.collection('posts').add({ title: 'title10', body: 'body10', likesCount: 39, category: 'life' })
await db.collection('posts').add({ title: 'title11', body: 'body11', likesCount: 21, category: 'music' })
await db.collection('posts').add({ title: 'title12', body: 'body12', likesCount: 43, category: 'science' })
postsコレクションに12ドキュメント登録されました。
単一ドキュメントを取得
- CollectionReference.doc(docId)
- DocumentReference.get()
- ( 戻り値: DocumentSnapshot )
postsコレクションの中から ドキュメントID=5H7wnLfbnupsWuFnsST1
のドキュメントを取得します。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const postRef = db.collection('posts').doc('AOPFbwazI7HoE2ofXm5Q')
const postDoc = await postRef.get() // firebase.firestore.DocumentSnapshotのインスタンスを取得
if (postDoc.exists) {
console.log(postDoc.id)
console.log(postDoc.data())
console.log(postDoc.get('title'))
} else {
console.log('No such document!')
}
await db.app.delete()
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
AOPFbwazI7HoE2ofXm5Q
{ body: 'body6',
category: 'science',
likesCount: 42,
title: 'title6' }
title6
複数ドキュメントを取得
- CollectionReference.get()
- ( 戻り値: QuerySnapshot )
postsコレクションの全ドキュメントを取得します。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const querySnapshot = await db.collection('posts')
.get() // firebase.firestore.QuerySnapshotのインスタンスを取得
console.log(querySnapshot.size)
console.log(querySnapshot.empty)
console.log(querySnapshot.docs.map(postDoc => postDoc.id))
querySnapshot.forEach((postDoc) => {
console.log(postDoc.id, ' => ', JSON.stringify(postDoc.data()))
})
await db.app.delete()
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
12
false
[ 'AOPFbwazI7HoE2ofXm5Q',
'CAy51oSyQVpoayfY54k9',
'OYCZTAtR7MNFnP1iMVX4',
'ThJZPUBdj95ozjfqgLJh',
'VvuGkWmH9Hkd3LorLUaI',
'dEEUHM65w1B1CNinrZPF',
'jaovhErVfrFudbkVEd8y',
'rHdnl1vDRjeXWD97paVc',
'u0FMUCZJeUMljyt4QJwO',
'u4g5Q58lbOROMy3TDvrF',
'vDJCwIo2vRpHf2jTkgaU',
'yu0IIVoaxzDtEEJ5vCBV' ]
AOPFbwazI7HoE2ofXm5Q => {"body":"body6","category":"science","likesCount":42,"title":"title6"}
CAy51oSyQVpoayfY54k9 => {"body":"body5","category":"music","likesCount":39,"title":"title5"}
OYCZTAtR7MNFnP1iMVX4 => {"body":"body3","category":"news","likesCount":40,"title":"title3"}
ThJZPUBdj95ozjfqgLJh => {"body":"body9","category":"news","likesCount":36,"title":"title9"}
VvuGkWmH9Hkd3LorLUaI => {"body":"body2","category":"news","likesCount":20,"title":"title2"}
dEEUHM65w1B1CNinrZPF => {"body":"body11","category":"music","likesCount":21,"title":"title11"}
jaovhErVfrFudbkVEd8y => {"body":"body7","category":"science","likesCount":29,"title":"title7"}
rHdnl1vDRjeXWD97paVc => {"body":"body1","category":"life","likesCount":50,"title":"title1"}
u0FMUCZJeUMljyt4QJwO => {"body":"body12","category":"science","likesCount":43,"title":"title12"}
u4g5Q58lbOROMy3TDvrF => {"body":"body10","category":"life","likesCount":39,"title":"title10"}
vDJCwIo2vRpHf2jTkgaU => {"body":"body4","category":"life","likesCount":28,"title":"title4"}
yu0IIVoaxzDtEEJ5vCBV => {"body":"body8","category":"life","likesCount":47,"title":"title8"}
条件指定
- CollectionReference.where(条件).get()
CollectionReferenceのwhereメソッド
で条件( <
<=
==
>
>=
array-contains
)指定できます。
category
が life
であるドキュメントを取得してみます。
const querySnapshot = await db.collection('posts')
.where('category', '==', 'life')
.get()
console.log(querySnapshot.size)
console.log(querySnapshot.empty)
querySnapshot.forEach((postDoc) => {
console.log(postDoc.id, ' => ', JSON.stringify(postDoc.data()))
})
4
false
rHdnl1vDRjeXWD97paVc => {"body":"body1","category":"life","likesCount":50,"title":"title1"}
u4g5Q58lbOROMy3TDvrF => {"body":"body10","category":"life","likesCount":39,"title":"title10"}
vDJCwIo2vRpHf2jTkgaU => {"body":"body4","category":"life","likesCount":28,"title":"title4"}
yu0IIVoaxzDtEEJ5vCBV => {"body":"body8","category":"life","likesCount":47,"title":"title8"}
条件指定
- 複合インデックスが必要なケース
Firestoreには、以下の2種類のインデックスがあります。
- 単一フィールドインデックス(自動で設定)
- 複合インデックス
以下のように複数フィールドに対してwhereを指定する場合、複合インデックスが必要です。
const querySnapshot = await db.collection('posts')
.where('category', '==', 'life')
.where('likesCount', '>', 40)
.get()
並べ替え・件数指定
- CollectionReference.orderBy()
- CollectionReference.limit()
likesCount
の降順で並べ替えて、5件取得してみます。
const querySnapshot = await db.collection('posts')
.orderBy('likesCount', 'desc')
.limit(5)
.get()
console.log(querySnapshot.size)
console.log(querySnapshot.empty)
querySnapshot.forEach((postDoc) => {
console.log(postDoc.id, ' => ', JSON.stringify(postDoc.data()))
})
5
false
rHdnl1vDRjeXWD97paVc => {"body":"body1","category":"life","likesCount":50,"title":"title1"}
yu0IIVoaxzDtEEJ5vCBV => {"body":"body8","category":"life","likesCount":47,"title":"title8"}
u0FMUCZJeUMljyt4QJwO => {"body":"body12","category":"science","likesCount":43,"title":"title12"}
AOPFbwazI7HoE2ofXm5Q => {"body":"body6","category":"science","likesCount":42,"title":"title6"}
OYCZTAtR7MNFnP1iMVX4 => {"body":"body3","category":"news","likesCount":40,"title":"title3"}
ページング
( 開始点, 終了点 )
startAt()
startAfter()
を利用してクエリの開始点を指定できます。endAt()
endBefore()
を利用してクエリの終了点を指定できます。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const getPosts = async (cursor = null, type = '') => {
let postRef = db.collection('posts')
.orderBy('likesCount', 'desc')
if (cursor) {
console.log(`type: ${type}`)
console.log(`cursor: ${cursor.data().title}`)
if (type === 'startAt') {
postRef = postRef.startAt(cursor)
}
if (type === 'startAfter') {
postRef = postRef.startAfter(cursor)
}
if (type === 'endAt') {
postRef = postRef.endAt(cursor)
}
if (type === 'endBefore') {
postRef = postRef.endBefore(cursor)
}
}
const querySnapshot = await postRef.get()
console.log(`docs: ${querySnapshot.docs.map(postDoc => postDoc.data().title)}`)
console.log()
return querySnapshot.docs
}
const postDocs = await getPosts()
await getPosts(postDocs[5], 'startAt')
await getPosts(postDocs[5], 'startAfter')
await getPosts(postDocs[5], 'endAt')
await getPosts(postDocs[5], 'endBefore')
await db.app.delete()
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
docs: title1,title8,title12,title6,title3,title10,title5,title9,title7,title4,title11,title2
type: startAt
cursor: title10
docs: title10,title5,title9,title7,title4,title11,title2
type: startAfter
cursor: title10
docs: title5,title9,title7,title4,title11,title2
type: endAt
cursor: title10
docs: title1,title8,title12,title6,title3,title10
type: endBefore
cursor: title10
docs: title1,title8,title12,title6,title3
ページング処理例
以下のようにページング処理を実装できます。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const getPosts = async (cursor, limit) => {
let postRef = db.collection('posts')
.orderBy('likesCount', 'desc')
.limit(limit)
if (cursor) {
postRef = postRef.startAfter(cursor)
}
const querySnapshot = await postRef.get()
const lastVisibleDoc = !querySnapshot.empty
? querySnapshot.docs[querySnapshot.docs.length - 1]
: null
return {
docs: querySnapshot.docs,
cursor: lastVisibleDoc
}
}
let cursor = null
while (true) {
console.log('########################################')
const postDocs = await getPosts(cursor, 3)
cursor = postDocs.cursor
console.log(`docs: ${postDocs.docs.map(postDoc => postDoc.id)}`)
console.log(`cursor: ${cursor && cursor.id}`)
if (!cursor) {
break
}
}
await db.app.delete()
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
3ドキュメントずつ読み込んでいます。
########################################
docs: rHdnl1vDRjeXWD97paVc,yu0IIVoaxzDtEEJ5vCBV,u0FMUCZJeUMljyt4QJwO
cursor: u0FMUCZJeUMljyt4QJwO
########################################
docs: AOPFbwazI7HoE2ofXm5Q,OYCZTAtR7MNFnP1iMVX4,u4g5Q58lbOROMy3TDvrF
cursor: u4g5Q58lbOROMy3TDvrF
########################################
docs: CAy51oSyQVpoayfY54k9,ThJZPUBdj95ozjfqgLJh,jaovhErVfrFudbkVEd8y
cursor: jaovhErVfrFudbkVEd8y
########################################
docs: vDJCwIo2vRpHf2jTkgaU,dEEUHM65w1B1CNinrZPF,VvuGkWmH9Hkd3LorLUaI
cursor: VvuGkWmH9Hkd3LorLUaI
########################################
docs:
cursor: null
削除
ドキュメントを削除
- CollectionReference.doc(docId)
- DocumentReference.delete()
DocumentReferenceのdeleteメソッド
で対象ドキュメントを削除できます。
await db.collection('users')
.doc(documentPath)
.delete()
ドキュメント内の一部データを削除
- FieldValue.delete()
下記ドキュメントの score
のみ削除してみます。
一部データを削除する場合、FieldValue.delete
を利用します。
const userRef = db.collection('users').doc('KRZ5TTHs4Xwjo7XK9t59')
await userRef.update({
score: firebase.firestore.FieldValue.delete(),
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
})
以下、実行結果です。
コレクションを削除
コレクション内のすべてのドキュメントが削除されれば削除されます。
複数ドキュメントに対してアトミック操作
トランザクション( Transaction )
( 読み込み後、書きこみ )
トランザクションを利用する場合、書き込み操作
より 読み取り操作
を先に実行する必要があります。
const collectionADocRef = db.collection('collectionB').doc('XXXXXXXXXXXXXXXXXXXX')
const collectionBDocRef = db.collection('collectionB').doc('YYYYYYYYYYYYYYYYYYYY')
await db.runTransaction(async transaction => {
const [collectionADoc, collectionBDoc] = await Promise.all([
transaction.get(collectionADocRef),
transaction.get(collectionBDocRef),
])
transaction.update(collectionADocRef, { title: `xxx_${collectionADoc.get('name')}` })
transaction.update(collectionBDocRef, { name: `xxx_${collectionBDoc.get('name')}` })
})
バッチで一括書き込み( WriteBatch )
( 書き込みのみ )
Firestore.batch()
を呼び出すと WriteBatchのオブジェクト
を取得できます。
(async () => {
try {
// 省略
// (Cloud Firestoreのインスタンスを初期化してdbにセット)
const batch = db.batch()
batch.set(
db.collection('posts').doc('7is0l08G9OtjedDt5o1h'),
{title: 'xxx'}
)
batch.update(
db.collection('posts').doc('et7WHlN49VaRmpINjwda'),
{title: 'yyy'}
)
batch.delete(
db.collection('posts').doc('JzsIRSiWVixyUuyZQN7m')
)
await batch.commit()
await db.app.delete()
} catch (err) {
console.log(`Error: ${JSON.stringify(err)}`)
}
})()
参考
- 追加・更新
- 取得
- 削除
- トランザクション
- インデックス
- APIリファレンス