Apply your knowledge to build something amazing!
:information_source: Project Overview Difficulty Level: Intermediate
Estimated Time: 3-4 hours
Skills Practiced:
- Firebase Authentication (Email/Password)
- Firestore Database operations
- SvelteKit routing and navigation guards
- Real-time data synchronization
- User interface state management
In this lesson, we will be creating a Mood Tracker application that helps users monitor their emotional well-being over time. This project combines authentication with database operations to create a personalized experience for each user.
:bulb: What You'll Build A fully functional mood tracking application where users can:
- Create secure accounts and log in
- Record their daily mood on a scale of 1-10
- View their mood history with timestamps
- See visual representations of their emotional states
graph LR
A[Project Setup] --> B[Authentication]
B --> C[Database Integration]
C --> D[UI Implementation]
D --> E[Testing & Deployment]
B --> B1[Logout Function]
B --> B2[Sign Up Page]
B --> B3[Login Page]
B --> B4[Navigation Guards]
C --> C1[Firestore Setup]
C --> C2[Add Mood Entries]
C --> C3[Retrieve Entries]
D --> D1[Mood Input UI]
D --> D2[History Display]
D --> D3[Styling]
In order to complete this project, we would require Firebase Authentication and Firestore Database.
:warning: Important Setup Requirements Before starting this project, ensure you have:
- Created a Firebase project at https://firebase.google.com/
- Enabled Email/Password authentication in Firebase Console
- Created a Firestore Database in your Firebase project
- Obtained your Firebase configuration credentials
Common Mistake: Forgetting to enable authentication methods in Firebase Console will cause login/signup to fail silently!
:bulb: Project Structure
- Parts 1-4: Focus on Firebase Authentication implementation
- Part 5: Integrates Firestore Database for mood storage
- Each part builds upon the previous one, so complete them in order!
DO NOT DELETE the existing files in the template:
ONLY EDIT the necessary files.
:warning: Common Project Setup Mistakes
- Deleting template files: This will break the project structure
- Wrong Firebase config: Double-check your Firebase credentials
- Missing npm packages: Run
npm install
if you see import errors- Incorrect file paths: Always create components in the correct folders
Let's make the homepage (index.svelte
) as the page for the app when the user has already logged in.
Add a Logout button on the homepage:
index.svelte
{/* Sign Out */}
<section class="container px-4 py-3 text-center">
<button class="btn btn-secondary" on:click=\{logout\}>Logout</button>
</section>
Import the necessary Firebase modules and stores:
<script>
import \{auth\} from '../firebase';
import \{signOut\} from 'firebase/auth';
import authStore from '../stores/authStore';
import \{onDestroy\} from 'svelte'; //Go back to login page when the user is inactive to avoid memory leaks
import \{ goto \} from '$app/navigation'; // Navigate the page to another component
//More Codes to be Added
</script>
:bulb: Understanding the Imports
- auth: Your Firebase authentication instance
- signOut: Firebase method to log users out
- authStore: Svelte store managing authentication state
- onDestroy: Lifecycle hook for cleanup
- goto: SvelteKit's navigation function
When the button is clicked, the form will be submitted by calling the logout
function, so the user can be logged out from the page and the firebase login status is changed to false:
async function logout()\{
try\{
await signOut(auth);
$authStore.isLoggedIn = false;
$authStore.firebaseControlled = false;
\}catch(err)\{
console.log(err);
\}
\}
:warning: Common Authentication Errors If logout fails, check:
- Firebase not initialized: Ensure firebase.js exports auth correctly
- Store not updating: Verify authStore is imported properly
- Console errors: Always check browser console for Firebase error messages
:white_check_mark: Checkpoint: Test your logout button before proceeding to ensure authentication is working!
In this example we will be using Email & Password for authentication.
First, we will need a signup page. Let's create a new Svelte component in the routes folder called signup.svelte
file as our signup page (already given, no need to copy):
signup.svelte
<div class="card mt-5 m-auto text-center" style={{'width':'24em'}}>
<div class="card-body">
<h5 class="card-title">Welcome</h5>
<p class="card-text text-muted">Please sign up to continue...</p>
<form class="form-floating" on:submit|preventDefault=\{register\}>
<div class="mb-3">
<label for="emailInput" class="form-label">Email address</label>
<input bind:value=\{email\} type="email" class="form-control" id="emailInput" />
</div>
<div class="mb-3">
<label for="passwordInput" class="form-label">Password</label>
<input
bind:value=\{password\}
type="password"
class="form-control"
id="passwordInput"
aria-describedby="passwordHelp"
/>
</div>
<div id="passwordHelp" class="form-text">
Your password must be at least 6 characters long.
</div>
<button type="submit" class="btn btn-dark mt-3">Sign Up</button>
</form>
</div>
</div>
<div class="text-center mt-3">
<a href="/login" class="text-muted">Already have an account? Click here to login.</a>
</div>
Here you can see we have a basic Bootstrap form that has an email input, password input and a signup button.
Below the page there is a link to go to the login page if you already have an account.
Import the Firebase authentication module:
<script>
import \{createUserWithEmailAndPassword\} from 'firebase/auth';
import \{auth\} from '../firebase';
import \{ goto \} from '$app/navigation';
import authStore from '../stores/authStore';
import \{ onDestroy \} from 'svelte';
let email = ""; //Create a variable named as email
let password = ""; //Create a variable named as password
async function register()\{
try\{
await createUserWithEmailAndPassword(auth, email, password);
\}catch(error)\{
console.log(error);
\}
\}
</script>
The form binds the input to email and password respectively using Svelte Data Binding.
:bulb: Password Requirements Firebase requires passwords to be at least 6 characters long. Consider adding these best practices:
- Show password strength indicator
- Add confirm password field
- Display error messages for weak passwords
:white_check_mark: Checkpoint: Create a test account and verify it appears in Firebase Console!
Now that we have a signup page, we will create another new route called login.svelte
file in the src/routes
folder. This page is where the user goes to sign in if they already have an account (already given, no need to copy):
login.svelte
<div class="card mt-5 m-auto text-center" style={{'width':'24em'}}>
<div class="card-body">
<h5 class="card-title">Welcome Back</h5>
<p class="card-text text-muted">Please log-in to continue...</p>
<form class="form-floating" on:submit|preventDefault=\{login\}>
<div class="mb-3">
<label for="emailInput" class="form-label">Email address</label>
<input bind:value=\{email\} type="email" class="form-control" id="emailInput" />
</div>
<div class="mb-3">
<label for="passwordInput" class="form-label">Password</label>
<input
bind:value=\{password\}
type="password"
class="form-control"
id="passwordInput"
aria-describedby="passwordHelp"
/>
</div>
<div id="passwordHelp" class="form-text">
Your password must be at least 6 characters long.
</div>
<button type="submit" class="btn btn-dark mt-3">Login</button>
</form>
</div>
</div>
<div class="text-center mt-3">
<a href="/signup" class="text-muted">First time? Click here to register for an account.</a>
</div>
Import the Firebase login authentication module:
<script>
import \{signInWithEmailAndPassword\} from 'firebase/auth';
import \{auth\} from '../firebase';
import \{ goto \} from '$app/navigation';
import authStore from '../stores/authStore';
import \{ onDestroy \} from 'svelte';
let email = "";
let password = "";
async function login()\{
try\{
await signInWithEmailAndPassword(auth, email, password);
\}catch(error)\{
console.log(error);
\}
\}
</script>
:warning: Login Troubleshooting Common login issues:
- "User not found": Check if account exists in Firebase Console
- "Invalid password": Passwords are case-sensitive
- "Too many attempts": Firebase may temporarily block after multiple failed attempts
We need to create a navigation guard to redirect users to the appropriate pages based on their authentication status.
Add this code to both login and signup pages to handle automatic navigation:
const sub = authStore.subscribe(async (info) => \{
if (info.isLoggedIn) \{
await goto('/');
\}
\});
onDestroy(() => \{
sub();
\});
Add this to the main index.svelte
page to redirect unauthenticated users:
const sub = authStore.subscribe(async (info) => \{
if (!info.isLoggedIn && info.firebaseControlled) \{
await goto('/login');
\}
\});
onDestroy(() => \{
sub();
\});
:bulb: Navigation Guard Best Practices
- Always unsubscribe from stores in
onDestroy
to prevent memory leaks- Use
firebaseControlled
flag to prevent redirects before Firebase initializes- Test navigation by logging out and trying to access protected pages
:white_check_mark: Checkpoint: Verify that:
Now let's add the mood tracking functionality with Firestore database.
:warning: Firestore Setup Required Before proceeding, ensure you have:
- Created a Firestore database in Firebase Console
- Set security rules to allow authenticated users to read/write their own data
- Exported
db
from your firebase.js fileCommon Error: "Missing or insufficient permissions" means your security rules need updating!
Add these variables to your main index.svelte
:
import \{ collection, addDoc, getDocs, query, where, orderBy \} from 'firebase/firestore';
import \{ db \} from '../firebase';
let moodScale = 5;
let moodEntries = [];
let loading = false;
:bulb: Variable Explanations
- moodScale: Current mood value (1-10)
- moodEntries: Array to store all mood history
- loading: Prevents multiple submissions
Create a function to add mood entries to Firestore:
async function addMoodEntry() \{
if (!$authStore.user) return;
loading = true;
try \{
await addDoc(collection(db, 'moods'), \{
userId: $authStore.user.uid,
mood: moodScale,
date: new Date().toISOString(),
timestamp: new Date()
\});
// Refresh the mood entries
await getMoodEntries();
// Reset mood scale
moodScale = 5;
\} catch (error) \{
console.error("Error adding mood entry:", error);
\}
loading = false;
\}
Create a function to retrieve mood entries from Firestore:
async function getMoodEntries() \{
if (!$authStore.user) return;
try \{
const q = query(
collection(db, 'moods'),
where('userId', '==', $authStore.user.uid),
orderBy('timestamp', 'desc')
);
const querySnapshot = await getDocs(q);
moodEntries = querySnapshot.docs.map(doc => (\{
id: doc.id,
...doc.data(),
date: new Date(doc.data().date).toLocaleDateString()
\}));
\} catch (error) \{
console.error("Error getting mood entries:", error);
\}
\}
Add this HTML to your main page:
:bulb: UI Implementation Tips
- The range input provides an intuitive way to select mood
- Visual feedback with emojis helps users understand the scale
- Disabled state on button prevents duplicate submissions
<main class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2 class="text-center">Mood Tracker</h2>
</div>
<div class="card-body">
{/* Mood Input Section */}
<div class="mb-4">
<h4>How are you feeling today?</h4>
<div class="mood-scale">
<label for="moodRange" class="form-label">
Mood Scale: \{moodScale\}/10
</label>
<input
type="range"
class="form-range"
min="1"
max="10"
bind:value=\{moodScale\}
id="moodRange"
/>
<div class="d-flex justify-content-between">
<small>:'( Very Sad</small>
<small>😐 Neutral</small>
<small>:) Very Happy</small>
</div>
</div>
<button
class="btn btn-primary mt-3"
on:click=\{addMoodEntry\}
disabled=\{loading\}
>
\{loading ? 'Saving...' : 'Save Mood'\}
</button>
</div>
{/* Mood History Section */}
<div class="mood-history">
<h4>Your Mood History</h4>
\{#if moodEntries.length > 0\}
<div class="mood-entries">
\{#each moodEntries as entry\}
<div class="mood-entry">
<div class="mood-date">\{entry.date\}</div>
<div class="mood-value">
<span class="mood-number">\{entry.mood\}/10</span>
<span class="mood-emoji">
\{#if entry.mood <= 3\}:'(
\{:else if entry.mood <= 6\}😐
\{:else\}:)
\{/if\}
</span>
</div>
</div>
\{/each\}
</div>
\{:else\}
<p class="text-muted">No mood entries yet. Add your first mood above!</p>
\{/if\}
</div>
</div>
</div>
</div>
</div>
</main>
Add these styles to enhance the mood tracker appearance:
<style>
.mood-scale \{
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
margin: 20px 0;
\}
.mood-entries \{
max-height: 400px;
overflow-y: auto;
\}
.mood-entry \{
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
margin: 10px 0;
background-color: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #007bff;
\}
.mood-date \{
font-weight: bold;
color: #495057;
\}
.mood-value \{
display: flex;
align-items: center;
gap: 10px;
\}
.mood-number \{
font-weight: bold;
color: #007bff;
\}
.mood-emoji \{
font-size: 1.5em;
\}
.form-range \{
margin: 15px 0;
\}
</style>
:white_check_mark: Checkpoint: Your mood tracker should now:
Here are the key implementation steps:
<script>
import \{auth, db\} from '../firebase';
import \{signOut\} from 'firebase/auth';
import authStore from '../stores/authStore';
import \{onDestroy, onMount\} from 'svelte';
import \{ goto \} from '$app/navigation';
import \{ collection, addDoc, getDocs, query, where, orderBy \} from 'firebase/firestore';
let moodScale = 5;
let moodEntries = [];
let loading = false;
// Authentication guard
const sub = authStore.subscribe(async (info) => \{
if (!info.isLoggedIn && info.firebaseControlled) \{
await goto('/login');
\}
\});
// Load mood entries when user is authenticated
$: if ($authStore.user) \{
getMoodEntries();
\}
async function logout()\{
try\{
await signOut(auth);
$authStore.isLoggedIn = false;
$authStore.firebaseControlled = false;
\}catch(err)\{
console.log(err);
\}
\}
async function addMoodEntry() \{
if (!$authStore.user) return;
loading = true;
try \{
await addDoc(collection(db, 'moods'), \{
userId: $authStore.user.uid,
mood: moodScale,
date: new Date().toISOString(),
timestamp: new Date()
\});
await getMoodEntries();
moodScale = 5;
\} catch (error) \{
console.error("Error adding mood entry:", error);
\}
loading = false;
\}
async function getMoodEntries() \{
if (!$authStore.user) return;
try \{
const q = query(
collection(db, 'moods'),
where('userId', '==', $authStore.user.uid),
orderBy('timestamp', 'desc')
);
const querySnapshot = await getDocs(q);
moodEntries = querySnapshot.docs.map(doc => (\{
id: doc.id,
...doc.data(),
date: new Date(doc.data().date).toLocaleDateString()
\}));
\} catch (error) \{
console.error("Error getting mood entries:", error);
\}
\}
onDestroy(() => \{
sub();
\});
</script>
{/* Sign Out */}
<section class="container px-4 py-3 text-center">
<button class="btn btn-secondary" on:click=\{logout\}>Logout</button>
</section>
<main class="container mt-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h2 class="text-center">Mood Tracker</h2>
</div>
<div class="card-body">
{/* Mood Input Section */}
<div class="mb-4">
<h4>How are you feeling today?</h4>
<div class="mood-scale">
<label for="moodRange" class="form-label">
Mood Scale: \{moodScale\}/10
</label>
<input
type="range"
class="form-range"
min="1"
max="10"
bind:value=\{moodScale\}
id="moodRange"
/>
<div class="d-flex justify-content-between">
<small>:'( Very Sad</small>
<small>😐 Neutral</small>
<small>:) Very Happy</small>
</div>
</div>
<button
class="btn btn-primary mt-3"
on:click=\{addMoodEntry\}
disabled=\{loading\}
>
\{loading ? 'Saving...' : 'Save Mood'\}
</button>
</div>
{/* Mood History Section */}
<div class="mood-history">
<h4>Your Mood History</h4>
\{#if moodEntries.length > 0\}
<div class="mood-entries">
\{#each moodEntries as entry\}
<div class="mood-entry">
<div class="mood-date">\{entry.date\}</div>
<div class="mood-value">
<span class="mood-number">\{entry.mood\}/10</span>
<span class="mood-emoji">
\{#if entry.mood <= 3\}:'(
\{:else if entry.mood <= 6\}😐
\{:else\}:)
\{/if\}
</span>
</div>
</div>
\{/each\}
</div>
\{:else\}
<p class="text-muted">No mood entries yet. Add your first mood above!</p>
\{/if\}
</div>
</div>
</div>
</div>
</div>
</main>
<style>
.mood-scale \{
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
margin: 20px 0;
\}
.mood-entries \{
max-height: 400px;
overflow-y: auto;
\}
.mood-entry \{
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
margin: 10px 0;
background-color: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #007bff;
\}
.mood-date \{
font-weight: bold;
color: #495057;
\}
.mood-value \{
display: flex;
align-items: center;
gap: 10px;
\}
.mood-number \{
font-weight: bold;
color: #007bff;
\}
.mood-emoji \{
font-size: 1.5em;
\}
.form-range \{
margin: 15px 0;
\}
</style>
:warning: Common Issues & Solutions
"Cannot read property 'uid' of null"
- Ensure user is authenticated before accessing
$authStore.user.uid
- Add null checks:
if (!$authStore.user) return;
"Missing or insufficient permissions"
- Update Firestore security rules:
javascript
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /moods/{document} { allow read, write: if request.auth != null && request.auth.uid == resource.data.userId; allow create: if request.auth != null; } } }
Mood entries not loading
- Check browser console for errors
- Verify collection name matches ('moods')
- Ensure orderBy field exists in documents
Authentication redirects not working
- Verify authStore is properly imported
- Check that firebaseControlled is set correctly
- Test with console.log in subscription callbacks
Add charts and analytics to track mood patterns over time:
// Calculate average mood for the week
function getWeeklyAverage() \{
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
const weekEntries = moodEntries.filter(entry =>
new Date(entry.date) >= weekAgo
);
if (weekEntries.length === 0) return 0;
const sum = weekEntries.reduce((acc, entry) => acc + entry.mood, 0);
return (sum / weekEntries.length).toFixed(1);
\}
Allow users to add notes with their mood entries:
let moodNote = "";
// Update addMoodEntry function
await addDoc(collection(db, 'moods'), \{
userId: $authStore.user.uid,
mood: moodScale,
note: moodNote,
date: new Date().toISOString(),
timestamp: new Date()
\});
:bulb: Implementation Ideas
- Add a textarea below the mood slider for notes
- Make notes searchable in mood history
- Add word cloud visualization of common note themes
Mood Tracker (Code Review) https://youtu.be/mood-tracker-review
Code with AI: Enhance your mood tracker.
Prompts:
Before submitting your project, verify:
:bulb: Testing Checklist
- User can create a new account
- User can log in with existing credentials
- Logout button works correctly
- Navigation guards redirect properly
- Mood entries save to Firestore
- Mood history displays correctly
- UI is responsive on mobile devices
- No console errors in browser
- Firebase security rules are configured
- All authentication flows work smoothly
When you have completed your project, submit it using the link below:
:warning: Submission Requirements Make sure to:
- Test all features thoroughly
- Include your Firebase project URL in submission
- Ensure your app is accessible (check security rules)
- Take screenshots of your working application
:tada: Congratulations! You've built a full-stack mood tracking application with authentication and real-time database synchronization!