Svelte + Firebase
FirebaseはGoogleが提供するBaaS(Backend as a Service)で、認証、データベース、ストレージなどの機能を提供します。Svelteと組み合わせることで、バックエンド開発なしに高機能なWebアプリを構築できます。
セットアップ
1. プロジェクト作成
npm create vite@latest my-svelte-firebase -- --template svelte-ts
cd my-svelte-firebase
npm install firebase bash
2. Firebase設定
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID
};
// Firebaseアプリの初期化
const app = initializeApp(firebaseConfig);
// 各サービスのエクスポート
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app); typescript
認証の実装
Google認証コンポーネント
<!-- src/lib/components/GoogleAuth.svelte -->
<script lang="ts">
import { signInWithPopup, GoogleAuthProvider, signOut, type User } from 'firebase/auth';
import { auth } from '$lib/firebase';
import { onMount } from 'svelte';
let user = $state<User | null>(null);
let loading = $state(true);
onMount(() => {
// 認証状態の監視
const unsubscribe = auth.onAuthStateChanged((currentUser) => {
user = currentUser;
loading = false;
});
return unsubscribe;
});
async function handleGoogleLogin() {
const provider = new GoogleAuthProvider();
try {
const result = await signInWithPopup(auth, provider);
console.log('ログイン成功:', result.user);
} catch (error) {
console.error('ログインエラー:', error);
}
}
async function handleLogout() {
try {
await signOut(auth);
console.log('ログアウト成功');
} catch (error) {
console.error('ログアウトエラー:', error);
}
}
</script>
{#if loading}
<p>読み込み中...</p>
{:else if user}
<div class="user-info">
<img src={user.photoURL || ''} alt={user.displayName || ''} />
<span>{user.displayName}</span>
<button onclick={handleLogout}>ログアウト</button>
</div>
{:else}
<button onclick={handleGoogleLogin}>Googleでログイン</button>
{/if}
<style>
.user-info {
display: flex;
align-items: center;
gap: 1rem;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
}
</style> svelte
Firestoreでのリアルタイムデータ
TODOリストの実装
<!-- src/lib/components/TodoList.svelte -->
<script lang="ts">
import {
collection,
addDoc,
deleteDoc,
doc,
onSnapshot,
query,
orderBy,
serverTimestamp,
type DocumentData
} from 'firebase/firestore';
import { db, auth } from '$lib/firebase';
import { onMount } from 'svelte';
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
userId: string;
}
let todos = $state<Todo[]>([]);
let newTodo = $state('');
let unsubscribe: (() => void) | null = null;
onMount(() => {
if (!auth.currentUser) return;
// リアルタイム更新の購読
const q = query(
collection(db, 'todos'),
orderBy('createdAt', 'desc')
);
unsubscribe = onSnapshot(q, (snapshot) => {
todos = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
} as Todo));
});
return () => {
unsubscribe?.();
};
});
async function addTodo() {
if (!newTodo.trim() || !auth.currentUser) return;
try {
await addDoc(collection(db, 'todos'), {
text: newTodo,
completed: false,
createdAt: serverTimestamp(),
userId: auth.currentUser.uid
});
newTodo = '';
} catch (error) {
console.error('追加エラー:', error);
}
}
async function deleteTodo(id: string) {
try {
await deleteDoc(doc(db, 'todos', id));
} catch (error) {
console.error('削除エラー:', error);
}
}
</script>
<div class="todo-container">
<form onsubmit={(e) => { e.preventDefault(); addTodo(); }}>
<input
bind:value={newTodo}
placeholder="新しいTODOを入力"
/>
<button type="submit">追加</button>
</form>
<ul>
{#each todos as todo (todo.id)}
<li>
<span>{todo.text}</span>
<button onclick={() => deleteTodo(todo.id)}>削除</button>
</li>
{/each}
</ul>
</div> svelte
Cloud Storageでのファイルアップロード
// src/lib/utils/storage.ts
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { storage } from '$lib/firebase';
export async function uploadFile(
file: File,
path: string
): Promise<string> {
const storageRef = ref(storage, path);
const snapshot = await uploadBytes(storageRef, file);
return getDownloadURL(snapshot.ref);
}
// 使用例
async function handleFileUpload(event: Event) {
const input = event.target as HTMLInputElement;
if (!input.files?.[0]) return;
const file = input.files[0];
const path = `uploads/${Date.now()}_${file.name}`;
try {
const url = await uploadFile(file, path);
console.log('アップロード完了:', url);
} catch (error) {
console.error('アップロードエラー:', error);
}
} typescript
デプロイ
Firebase Hostingへのデプロイ
# Firebase CLIのインストール
npm install -g firebase-tools
# Firebaseプロジェクトの初期化
firebase init hosting
# ビルド
npm run build
# デプロイ
firebase deploy --only hosting bash
ベストプラクティス
1. セキュリティルールの設定
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ユーザーが自分のTODOのみアクセス可能
match /todos/{todoId} {
allow read, write: if request.auth != null
&& request.auth.uid == resource.data.userId;
allow create: if request.auth != null
&& request.auth.uid == request.resource.data.userId;
}
}
} javascript
2. 環境変数の管理
# .env.local
VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_auth_domain
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_storage_bucket
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id bash
3. リアクティブなユーザー状態管理
// src/lib/stores/auth.svelte.ts
import { auth } from '$lib/firebase';
import { onAuthStateChanged, type User } from 'firebase/auth';
class AuthStore {
user = $state<User | null>(null);
loading = $state(true);
constructor() {
onAuthStateChanged(auth, (user) => {
this.user = user;
this.loading = false;
});
}
get isAuthenticated() {
return !!this.user;
}
}
export const authStore = new AuthStore(); typescript
4. エラーハンドリング
// src/lib/utils/firebase-errors.ts
export function getErrorMessage(error: any): string {
const errorCode = error?.code || 'unknown';
const messages: Record<string, string> = {
'auth/user-not-found': 'ユーザーが見つかりません',
'auth/wrong-password': 'パスワードが間違っています',
'auth/email-already-in-use': 'このメールアドレスは既に使用されています',
'auth/weak-password': 'パスワードが弱すぎます',
'auth/invalid-email': 'メールアドレスが無効です',
'permission-denied': 'アクセス権限がありません',
'unknown': '予期しないエラーが発生しました'
};
return messages[errorCode] || messages.unknown;
} typescript
注意点
Firebaseの設定情報は公開されても問題ありませんが、セキュリティルールの設定が重要です。 必ずFirestoreとStorageのルールを適切に設定してください。
パフォーマンス最適化
- Firestoreのクエリにはインデックスを設定
- 画像はCloud Storageにアップロード前にリサイズ
- リアルタイム更新は必要な箇所のみで使用
まとめ
Svelte + Firebaseの組み合わせは、以下のようなプロジェクトに最適です。
- ✅ プロトタイプ開発 - 素早くMVPを構築
- ✅ リアルタイムアプリ - チャット、コラボレーションツール
- ✅ 小〜中規模プロジェクト - スタートアップ、個人開発
- ✅ サーバーレス構成 - インフラ管理不要
次のステップとして、 Supabase統合 や GraphQL統合 も検討してみてください。