Firebase integration
Integrate Firebase into your Aurelia 2 application for real-time data, authentication, and cloud storage using Firebase's modular SDK.
Prerequisites
Aurelia 2 project
Firebase project setup
Firebase configuration details
Installation
npm install firebase
Configuration
Environment Variables
Add Firebase config to your environment:
// src/environment.ts
export const environment = {
firebase: {
apiKey: process.env.FIREBASE_API_KEY,
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
projectId: process.env.FIREBASE_PROJECT_ID,
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.FIREBASE_APP_ID
}
};
Firebase Service
Create a Firebase service with proper DI integration:
// src/services/firebase.ts
import { DI, Registration } from '@aurelia/kernel';
import { initializeApp, FirebaseApp } from 'firebase/app';
import { getAuth, Auth, signInWithEmailAndPassword, signOut, User, onAuthStateChanged } from 'firebase/auth';
import { getFirestore, Firestore } from 'firebase/firestore';
import { environment } from '../environment';
export const IFirebaseService = DI.createInterface<IFirebaseService>('IFirebaseService');
export interface IFirebaseService {
readonly app: FirebaseApp;
readonly auth: Auth;
readonly db: Firestore;
login(email: string, password: string): Promise<User>;
logout(): Promise<void>;
onAuthStateChanged(callback: (user: User | null) => void): () => void;
}
export class FirebaseService implements IFirebaseService {
readonly app: FirebaseApp;
readonly auth: Auth;
readonly db: Firestore;
constructor() {
this.app = initializeApp(environment.firebase);
this.auth = getAuth(this.app);
this.db = getFirestore(this.app);
}
async login(email: string, password: string): Promise<User> {
const result = await signInWithEmailAndPassword(this.auth, email, password);
return result.user;
}
async logout(): Promise<void> {
await signOut(this.auth);
}
onAuthStateChanged(callback: (user: User | null) => void): () => void {
return onAuthStateChanged(this.auth, callback);
}
}
export const FirebaseServiceRegistration = Registration.singleton(IFirebaseService, FirebaseService);
Register Service
Register the Firebase service in your main application:
// src/main.ts
import { Aurelia, StandardConfiguration } from '@aurelia/runtime-html';
import { FirebaseServiceRegistration } from './services/firebase';
import { MyApp } from './my-app';
const au = new Aurelia()
.register(
StandardConfiguration,
FirebaseServiceRegistration
);
au.app({ host: document.querySelector('my-app'), component: MyApp });
await au.start();
Authentication Component
// src/components/login.ts
import { customElement } from '@aurelia/runtime-html';
import { resolve } from '@aurelia/kernel';
import { IFirebaseService } from '../services/firebase';
@customElement('login')
export class Login {
email = '';
password = '';
user: any = null;
error = '';
private readonly firebase: IFirebaseService = resolve(IFirebaseService);
constructor() {
// Listen for auth state changes
this.firebase.onAuthStateChanged(user => {
this.user = user;
});
}
async login() {
try {
this.error = '';
const user = await this.firebase.login(this.email, this.password);
console.log('Logged in:', user.email);
} catch (error: any) {
this.error = error.message;
}
}
async logout() {
await this.firebase.logout();
}
}
<!-- src/components/login.html -->
<div class="login-form">
<div if.bind="!user">
<input value.bind="email" placeholder="Email" type="email">
<input value.bind="password" placeholder="Password" type="password">
<button click.trigger="login()" disabled.bind="!email || !password">
Login
</button>
<div if.bind="error" class="error">${error}</div>
</div>
<div if.bind="user">
<p>Welcome, ${user.email}!</p>
<button click.trigger="logout()">Logout</button>
</div>
</div>
Firestore Data Component
// src/components/todo-list.ts
import { customElement } from '@aurelia/runtime-html';
import { resolve } from '@aurelia/kernel';
import { collection, onSnapshot, addDoc, deleteDoc, doc, Unsubscribe } from 'firebase/firestore';
import { IFirebaseService } from '../services/firebase';
export interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
}
@customElement('todo-list')
export class TodoList {
todos: Todo[] = [];
newTodoText = '';
private unsubscribe?: Unsubscribe;
private readonly firebase: IFirebaseService = resolve(IFirebaseService);
attached() {
const todosCollection = collection(this.firebase.db, 'todos');
this.unsubscribe = onSnapshot(todosCollection, (snapshot) => {
this.todos = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
} as Todo));
});
}
detached() {
this.unsubscribe?.();
}
async addTodo() {
if (!this.newTodoText.trim()) return;
try {
await addDoc(collection(this.firebase.db, 'todos'), {
text: this.newTodoText,
completed: false,
createdAt: new Date()
});
this.newTodoText = '';
} catch (error) {
console.error('Error adding todo:', error);
}
}
async deleteTodo(todoId: string) {
try {
await deleteDoc(doc(this.firebase.db, 'todos', todoId));
} catch (error) {
console.error('Error deleting todo:', error);
}
}
}
<!-- src/components/todo-list.html -->
<div class="todo-list">
<div class="add-todo">
<input value.bind="newTodoText" placeholder="Add new todo"
keyup.trigger="$event.key === 'Enter' && addTodo()">
<button click.trigger="addTodo()" disabled.bind="!newTodoText.trim()">
Add
</button>
</div>
<ul class="todos">
<li repeat.for="todo of todos" class="todo-item">
<span class="todo-text">${todo.text}</span>
<button click.trigger="deleteTodo(todo.id)" class="delete-btn">
Delete
</button>
</li>
</ul>
<div if.bind="todos.length === 0" class="empty-state">
No todos yet. Add one above!
</div>
</div>
Best Practices
Security
Store Firebase config in environment variables
Use Firebase Security Rules to protect your data
Never expose sensitive data in client-side code
Performance
Clean up Firestore listeners in
detached()
lifecycleUse proper TypeScript interfaces for data structures
Leverage Firebase's offline capabilities
Error Handling
Always wrap Firebase operations in try-catch blocks
Provide user-friendly error messages
Log errors for debugging
Testing
Mock Firebase services for unit tests
Use Firebase Emulator Suite for integration tests
Test offline scenarios
This integration pattern provides type safety, proper lifecycle management, and follows Aurelia 2 best practices for dependency injection and component architecture.
Last updated
Was this helpful?