ゼロから Firestore データベースを設計するのは困難です。最初のコード行を書く前に、クエリ、非正規化、サブコレクション、その他数十の NoSQL 固有の懸念事項について考える必要があります。ゼロから始める代わりに、これらの10の実証済みパターンをテンプレートとして使用してください。それぞれが一般的なアプリアーキテクチャを表しています — e コマース、チャット、マルチテナント SaaS、位置ベースサービス — コレクションをモデル化する方法を正確に示す完全な JSON Schema 付きです。ユースケースに合ったパターンをコピーし、ニーズに合わせて適応させれば、数週間ではなく数分で堅固な基盤が得られます。
1. ユーザープロファイル
ソーシャルアプリ、SaaS プラットフォーム、メンバーシップサイト
Collection Structure
firestore_structures.pattern1_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "users",
"description": "User profiles with authentication info",
"schema": {
"type": "object",
"properties": {
"displayName": {
"type": "string",
"description": "User's full name"
},
"email": {
"type": "string",
"format": "email",
"description": "Login email address"
},
"photoURL": {
"type": "string",
"format": "uri",
"description": "Profile picture URL"
},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"],
"description": "Access control role"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Account creation timestamp"
}
},
"required": ["displayName", "email", "role", "createdAt"]
}
}Why this design: ユーザープロファイルをルートレベルのドキュメントとして保存し、Firebase Auth UID をドキュメント ID として使用します。displayName や photoURL などの頻繁にアクセスされるフィールドを非正規化して、余分な読み取りなしで他のコレクションに埋め込めるようにします。
Common mistake: ユーザー設定や環境設定を数百のアイテムがない限り、サブコレクションとしてネストしないでください。ユーザードキュメントを軽量で高速に読み取れるように保ちます。
2. e コマース製品と注文
オンラインストア、マーケットプレイス、在庫システム
Collection Structure
firestore_structures.pattern2_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "orders",
"description": "Customer purchase orders",
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string",
"description": "Reference to users collection"
},
"items": {
"type": "array",
"description": "Ordered items with denormalized product data",
"items": {
"type": "object",
"properties": {
"productId": { "type": "string" },
"name": { "type": "string" },
"price": { "type": "number", "minimum": 0 },
"quantity": { "type": "integer", "minimum": 1 }
},
"required": ["productId", "name", "price", "quantity"]
}
},
"total": {
"type": "number",
"minimum": 0,
"description": "Order total in USD"
},
"status": {
"type": "string",
"enum": ["pending", "confirmed", "shipped", "delivered"],
"description": "Current order status"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Order timestamp"
}
},
"required": ["userId", "items", "total", "status", "createdAt"]
}
}Why this design: 注文を userId でフィルタリングできるルートレベルのドキュメントとして保存します。製品名と価格を注文アイテムに非正規化して、後で製品データが変更されても履歴注文が正確なままになるようにします。
Common mistake: 在庫数を製品ドキュメントに保存しないでください — 人気商品の競合状態を防ぐために、別の在庫コレクションまたは Cloud Functions を使用してください。
3. チャットとメッセージング
リアルタイムチャットアプリ、カスタマーサポート、コラボレーションツール
Collection Structure
firestore_structures.pattern3_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "messages",
"description": "Chat messages within a room",
"schema": {
"type": "object",
"properties": {
"authorId": {
"type": "string",
"description": "User ID of message author"
},
"authorName": {
"type": "string",
"description": "Denormalized author display name"
},
"text": {
"type": "string",
"description": "Message content",
"maxLength": 5000
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Message timestamp"
},
"edited": {
"type": "boolean",
"description": "Whether message was edited"
}
},
"required": ["authorId", "authorName", "text", "createdAt"]
}
}Why this design: 各チャットルームが無制限のメッセージを保持できるように、メッセージにサブコレクションを使用します。すべてのメッセージ表示でユーザープロファイルを取得することを避けるために authorName を非正規化します。クイックプレビューレンダリングのために親 chatRoom に lastMessage を保存します。
Common mistake: 既読レシートやタイピングインジケータを Firestore ドキュメントで実装しようとしないでください — エフェメラルなプレゼンスデータには Realtime Database または Firebase Cloud Messaging を使用してください。
4. コメント付きブログ
コンテンツプラットフォーム、CMS、コミュニティフォーラム
Collection Structure
firestore_structures.pattern4_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "posts",
"description": "Blog posts with metadata",
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Post title",
"maxLength": 200
},
"content": {
"type": "string",
"description": "Post body in markdown"
},
"authorId": {
"type": "string",
"description": "Reference to users collection"
},
"authorName": {
"type": "string",
"description": "Denormalized author name"
},
"publishedAt": {
"type": "string",
"format": "date-time",
"description": "Publication timestamp"
},
"commentCount": {
"type": "integer",
"minimum": 0,
"description": "Cached comment count"
},
"tags": {
"type": "array",
"items": { "type": "string" },
"description": "Post tags for filtering"
}
},
"required": ["title", "content", "authorId", "authorName", "publishedAt"]
}
}Why this design: 効率的なリストレンダリングのために、非正規化された著者情報を持つルートドキュメントとして投稿を保存します。カウントを表示するためだけにコメントサブコレクション全体を読み取ることを避けるために、Cloud Functions を介して更新されるキャッシュされた commentCount を保持します。
Common mistake: 投稿ドキュメント内に配列として完全なコメントリストを保存しないでください — 数十のコメント以上になると壊れ、ページネーションが不可能になります。
5. マルチテナント SaaS
B2B アプリケーション、ワークスペースベースのツール、チーム管理
Collection Structure
firestore_structures.pattern5_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "organizations",
"description": "Tenant organizations for multi-tenant SaaS",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Organization name"
},
"plan": {
"type": "string",
"enum": ["free", "pro", "enterprise"],
"description": "Subscription plan"
},
"ownerId": {
"type": "string",
"description": "User ID of organization owner"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Organization creation timestamp"
},
"memberCount": {
"type": "integer",
"minimum": 1,
"description": "Cached member count"
}
},
"required": ["name", "plan", "ownerId", "createdAt"]
}
}Why this design: すべてのテナントデータをその下にネストした organizations をトップレベルコレクションとして使用します。これによりセキュリティルールが簡単になります:ユーザーは自分の組織内のデータにのみアクセスできます。簡単な列挙のために、メンバーロールをサブコレクションに保存します。
Common mistake: 組織データをユーザードキュメントに複製しないでください — 同期の問題が生じます。代わりに、ユーザーメンバーシップでフィルタリングされた organizations コレクションをクエリしてください。
6. アクティビティと監査ログ
コンプライアンス追跡、ユーザーアクティビティフィード、管理ダッシュボード
Collection Structure
firestore_structures.pattern6_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "activityLog",
"description": "Audit trail of user actions",
"schema": {
"type": "object",
"properties": {
"userId": {
"type": "string",
"description": "User who performed the action"
},
"userName": {
"type": "string",
"description": "Denormalized user name"
},
"action": {
"type": "string",
"enum": ["created", "updated", "deleted", "viewed"],
"description": "Action type"
},
"resourceType": {
"type": "string",
"description": "Type of resource affected (e.g., 'post', 'user')"
},
"resourceId": {
"type": "string",
"description": "ID of affected resource"
},
"metadata": {
"type": "object",
"description": "Additional context data"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "When the action occurred"
}
},
"required": ["userId", "action", "resourceType", "resourceId", "timestamp"]
}
}Why this design: userId、action、timestamp のインデックス付きフィールドを持つルートコレクションとしてアクティビティログを保存します。クライアントコードが失敗しても完全な監査証跡を確保するために、ドキュメント書き込み時に Cloud Functions を介してログエントリを生成します。
Common mistake: ログをユーザーまたはリソースの下のサブコレクションとして保存しようとしないでください — ユーザー間またはリソース間のクエリが不可能になります。参照フィールドを持つフラットなコレクションを使用してください。
7. 位置情報とマップ
位置ベースサービス、配達アプリ、店舗検索
Collection Structure
firestore_structures.pattern7_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "locations",
"description": "Locations with geospatial data",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Location name"
},
"address": {
"type": "string",
"description": "Street address"
},
"geopoint": {
"type": "object",
"description": "Firestore GeoPoint",
"properties": {
"latitude": { "type": "number", "minimum": -90, "maximum": 90 },
"longitude": { "type": "number", "minimum": -180, "maximum": 180 }
},
"required": ["latitude", "longitude"]
},
"geohash": {
"type": "string",
"description": "Geohash for proximity queries"
},
"category": {
"type": "string",
"enum": ["restaurant", "store", "office", "other"],
"description": "Location type"
}
},
"required": ["name", "geopoint", "geohash"]
}
}Why this design: 効率的な近接クエリを可能にするために、GeoPoint フィールドと並行して geohash 文字列を保存します。Firestore はネイティブの半径検索を実行できないため、geohash を使用してプレフィックスでフィルタリングして近くの場所を見つけることができます。geofire-common のようなライブラリを使用してハッシュを生成します。
Common mistake: 緯度/経度の範囲で直接クエリしようとしないでください — Firestore は複合不等式クエリを実行できません。位置ベースのフィルタリングには常に geohash プレフィックスを使用してください。
8. 在庫管理
倉庫システム、在庫追跡、サプライチェーンアプリ
Collection Structure
firestore_structures.pattern8_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "inventory",
"description": "Inventory items with stock levels",
"schema": {
"type": "object",
"properties": {
"sku": {
"type": "string",
"description": "Stock keeping unit code"
},
"name": {
"type": "string",
"description": "Item name"
},
"quantity": {
"type": "integer",
"minimum": 0,
"description": "Current stock quantity"
},
"warehouseId": {
"type": "string",
"description": "Warehouse location reference"
},
"reorderLevel": {
"type": "integer",
"minimum": 0,
"description": "Quantity threshold for reorder alerts"
},
"lastRestocked": {
"type": "string",
"format": "date-time",
"description": "Last restock timestamp"
}
},
"required": ["sku", "name", "quantity", "warehouseId"]
}
}Why this design: 競合状態を防ぐために、quantity を更新する際はトランザクションを使用します。頻繁な更新がある大量の在庫については、書き込みの競合を避けるために分散カウンタまたは別のトランザクションログコレクションの使用を検討してください。
Common mistake: トランザクションなしでクライアントコードから quantity フィールドを増減しないでください — 同時アクセス下で不正確なカウントになります。Cloud Functions またはサーバーサイドロジックを使用してください。
9. ソーシャルフィードとフォロワー
ソーシャルネットワーク、ニュースフィード、アクティビティストリーム
Collection Structure
firestore_structures.pattern9_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "posts",
"description": "Social feed posts",
"schema": {
"type": "object",
"properties": {
"authorId": {
"type": "string",
"description": "User ID of post author"
},
"authorName": {
"type": "string",
"description": "Denormalized author display name"
},
"content": {
"type": "string",
"description": "Post content",
"maxLength": 5000
},
"likeCount": {
"type": "integer",
"minimum": 0,
"description": "Cached like count"
},
"createdAt": {
"type": "string",
"format": "date-time",
"description": "Post timestamp"
}
},
"required": ["authorId", "authorName", "content", "createdAt"]
}
}Why this design: 両方向の効率的なクエリを可能にするために、各ユーザーの下に別々のサブコレクションとして followers と following を保存します。likes サブコレクション全体を読み取る代わりに、Cloud Functions を介して更新されるキャッシュされた likeCount を投稿に保持します。
Common mistake: 書き込み時にすべてのフォロワーのフィードに投稿をファンアウトしないでください — スケールしません。代わりに、following ID の array-contains クエリを使用してフォローしているユーザーからの投稿をリアルタイムでクエリしてください。
10. IoT とセンサーデータ
IoT ダッシュボード、環境モニタリング、デバイステレメトリ
Collection Structure
firestore_structures.pattern10_structure
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"collection": "readings",
"description": "Sensor readings from IoT devices",
"schema": {
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in Celsius"
},
"humidity": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Relative humidity percentage"
},
"batteryLevel": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Battery percentage"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "Reading timestamp"
}
},
"required": ["timestamp"]
}
}Why this design: 各デバイスの下のサブコレクションとして時系列データを保存します。高頻度データの場合、バッチ書き込みを実施し、無制限の増加を避けるために Cloud Functions を介して TTL クリーンアップを実装してください。長期分析には Cloud Firestore TTL ポリシーまたは BigQuery エクスポートを検討してください。
Common mistake: デバイスが毎秒報告する場合、すべてのセンサー読み取りを保存しないでください — Firestore の書き込み制限とコストを超えます。分または時間の間隔に集約するか、高頻度書き込みには Realtime Database を使用してください。
これらのパターンをドキュメントに変換する
これらの JSON Schema は単なる例ではありません — プロジェクトで使用できる実用的なドキュメントです。リポジトリに .schema.json ファイルとして保存し、FireSchema でレンダリングして、チームが実際に使用する閲覧可能なリファレンスを提供してください。セットアップには5分かかります。
次のステップ
パターンを手に入れたら、効果的に使用する方法を学びましょう: