mcpField
F
firebase-app-platform

firebase-app-platform

github.com/bagelhole開発1.2k314 回インストール23 週間前に更新
リポジトリ

firebase-app-platform について

Firebase のマネージドサービスでモバイル/Web バックエンドを構築するためのスキルガイドです。認証、リアルタイムデータ、サーバーレス API、ホスティングを扱う開発者向けに、設定と運用の例をまとめています。

主な機能

  1. 01Firestore のデータ操作とセキュリティルール
  2. 02Firebase Authentication の設定
  3. 03Cloud Functions によるサーバーレス API
  4. 04サイトと SPA のホスティング設定
  5. 05ローカルエミュレーターと複数環境の運用

活用シーン

  1. 01アプリにリアルタイムデータ同期を追加
  2. 02少ないバックエンドコードでサインインを設定
  3. 03Cloud Functions でサーバーレスエンドポイントを構築
  4. 04Firebase エミュレーターでローカルテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill firebase-app-platform

インストール後、Claude Code で/firebase-app-platformを入力して読み込まれたか確認します。

詳細

Firebase アプリプラットフォーム

Firebase のマネージドサービスを利用して、モバイルおよびウェブのバックエンドを構築できます。

このスキルを使う場面

次のような場合に、このスキルを使用してください。

  • リアルタイムのデータ同期が必要なモバイルアプリやウェブアプリを構築する
  • バックエンドコードを最小限に抑えて認証を実装する
  • マネージドインフラストラクチャを使って素早くプロトタイプを作成する
  • Cloud Functions でサーバーレス API を構築する
  • CDN を使って静的サイトや SPA をホスティングする

前提条件

  • Node.js 18+
  • Firebase CLI(npm install -g firebase-tools)
  • Google Cloud アカウント(Firebase は GCP の一部です)
  • Firebase プロジェクト(console.firebase.google.com で作成します)

クイックスタート

# Install and authenticate
npm install -g firebase-tools
firebase login

# Initialize in your project directory
firebase init
# Select: Firestore, Functions, Hosting, Emulators

# Start local emulators
firebase emulators:start

# Deploy everything
firebase deploy

# Deploy specific services
firebase deploy --only functions
firebase deploy --only hosting
firebase deploy --only firestore:rules

Firestore データベース

セキュリティルール
// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Users can only read/write their own data
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }

    // Messages: authenticated users can read, only owner can write
    match /channels/{channelId}/messages/{messageId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null
        && request.resource.data.userId == request.auth.uid
        && request.resource.data.body is string
        && request.resource.data.body.size() <= 5000;
      allow update, delete: if request.auth != null
        && resource.data.userId == request.auth.uid;
    }

    // Admin-only collection
    match /admin/{document=**} {
      allow read, write: if request.auth != null
        && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }

    // Default: deny everything
    match /{document=**} {
      allow read, write: if false;
    }
  }
}
データ操作
// lib/firestore.ts
import { getFirestore, collection, doc, setDoc, getDoc,
         query, where, orderBy, limit, onSnapshot,
         serverTimestamp, increment } from "firebase/firestore";

const db = getFirestore();

// Create document with auto-ID
async function createMessage(channelId: string, body: string, userId: string) {
  const ref = doc(collection(db, "channels", channelId, "messages"));
  await setDoc(ref, {
    body,
    userId,
    createdAt: serverTimestamp(),
  });
  return ref.id;
}

// Real-time listener
function subscribeToMessages(channelId: string, callback: (msgs: any[]) => void) {
  const q = query(
    collection(db, "channels", channelId, "messages"),
    orderBy("createdAt", "desc"),
    limit(50)
  );
  return onSnapshot(q, (snapshot) => {
    const messages = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
    callback(messages);
  });
}

// Atomic counter
async function incrementViews(postId: string) {
  await setDoc(doc(db, "posts", postId), {
    views: increment(1),
  }, { merge: true });
}
インデックス
// firestore.indexes.json
{
  "indexes": [
    {
      "collectionGroup": "messages",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "channelId", "order": "ASCENDING" },
        { "fieldPath": "createdAt", "order": "DESCENDING" }
      ]
    }
  ]
}

認証

// lib/auth.ts
import { getAuth, signInWithPopup, GoogleAuthProvider,
         createUserWithEmailAndPassword, signInWithEmailAndPassword,
         signOut, onAuthStateChanged } from "firebase/auth";

const auth = getAuth();

// Google sign-in
async function signInWithGoogle() {
  const provider = new GoogleAuthProvider();
  const result = await signInWithPopup(auth, provider);
  return result.user;
}

// Email/password registration
async function register(email: string, password: string) {
  const result = await createUserWithEmailAndPassword(auth, email, password);
  return result.user;
}

// Auth state listener
onAuthStateChanged(auth, (user) => {
  if (user) {
    console.log("Signed in:", user.uid, user.email);
  } else {
    console.log("Signed out");
  }
});

Cloud Functions

// functions/src/index.ts
import { onRequest } from "firebase-functions/v2/https";
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { getFirestore } from "firebase-admin/firestore";
import { initializeApp } from "firebase-admin/app";

initializeApp();
const db = getFirestore();

// HTTP function (API endpoint)
export const api = onRequest({ cors: true, region: "us-central1" }, async (req, res) => {
  if (req.method !== "GET") {
    res.status(405).send("Method not allowed");
    return;
  }
  const snapshot = await db.collection("posts").orderBy("createdAt", "desc").limit(10).get();
  const posts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
  res.json({ posts });
});

// Firestore trigger — runs when a new message is created
export const onMessageCreated = onDocumentCreated(
  "channels/{channelId}/messages/{messageId}",
  async (event) => {
    const data = event.data?.data();
    if (!data) return;

    // Update channel's last message timestamp
    await db.doc(`channels/${event.params.channelId}`).update({
      lastMessageAt: data.createdAt,
      messageCount: FieldValue.increment(1),
    });

    // Send notification (example)
    console.log(`New message in ${event.params.channelId}: ${data.body.substring(0, 50)}`);
  }
);

ホスティング

// firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      { "source": "/api/**", "function": "api" },
      { "source": "**", "destination": "/index.html" }
    ],
    "headers": [
      {
        "source": "**/*.@(js|css|svg|png|jpg|webp|woff2)",
        "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
      },
      {
        "source": "**",
        "headers": [
          { "key": "X-Frame-Options", "value": "DENY" },
          { "key": "X-Content-Type-Options", "value": "nosniff" },
          { "key": "Strict-Transport-Security", "value": "max-age=63072000" }
        ]
      }
    ]
  }
}

ローカルエミュレーター

# Start all emulators
firebase emulators:start

# Start specific emulators
firebase emulators:start --only auth,firestore,functions

# Export emulator data for persistence
firebase emulators:export ./emulator-data
firebase emulators:start --import=./emulator-data

# Emulator UI at http://localhost:4000
// firebase.json — emulator config
{
  "emulators": {
    "auth": { "port": 9099 },
    "firestore": { "port": 8080 },
    "functions": { "port": 5001 },
    "hosting": { "port": 5000 },
    "ui": { "enabled": true, "port": 4000 }
  }
}

環境設定

# Set environment variables for functions
firebase functions:config:set stripe.key="sk_live_xxx" app.name="MyApp"

# View config
firebase functions:config:get

# Use in functions (v1)
const stripeKey = functions.config().stripe.key;

# For v2 functions, use .env files
# functions/.env
STRIPE_KEY=sk_live_xxx

# functions/.env.local (for emulators)
STRIPE_KEY=sk_test_xxx

マルチ環境のセットアップ

# Create separate projects for each environment
firebase use --add   # Add staging project alias
firebase use staging # Switch to staging
firebase use production

# Deploy to specific project
firebase deploy --project my-app-staging
firebase deploy --project my-app-production

# .firebaserc
{
  "projects": {
    "staging": "my-app-staging",
    "production": "my-app-production"
  }
}

CLI リファレンス

firebase projects:list              # List all projects
firebase deploy                      # Deploy everything
firebase deploy --only functions     # Deploy only functions
firebase deploy --only hosting       # Deploy only hosting
firebase deploy --only firestore     # Deploy rules + indexes
firebase functions:log               # View function logs
firebase hosting:channel:create pr-123  # Preview channel
firebase hosting:channel:delete pr-123

セキュリティのベストプラクティス

  • 他のコードを書く前に、厳格な Firestore セキュリティルールを作成する
  • Firebase プロジェクトごとに環境を分ける(ステージング/本番)
  • GCP コンソールで予算アラートと割り当て量の監視を有効にする
  • 特権を要するロジックは Cloud Functions に移す(クライアントを決して信頼しない)
  • アプリ以外のクライアントによる API の不正利用を防ぐため、App Check を使用する
  • コンプライアンス対応のため、Firestore の監査ログを有効にする
  • OAuth 同意画面の設定を確認する

トラブルシューティング

問題解決方法
権限が拒否されるFirestore ルールを確認し、認証状態を検証します
関数のコールドスタート最小インスタンス(minInstances: 1)を使用し、インポートを最適化します
エミュレーターが起動しないポートの競合を確認し、firebase emulators:start --debug を実行します
デプロイに失敗するfirebase deploy --debug を実行し、サービスアカウントの権限を確認します
ルールのテストに失敗するfirebase emulators:exec を使用してルールの単体テストを実行します

関連スキル

コメント