Vue 3 + Pinia + Firebase CRUD App Tutorial

by Didin J. on May 20, 2025 Vue 3 + Pinia + Firebase CRUD App Tutorial

Learn how to build a Vue 3 CRUD app with Pinia and Firebase Firestore, including UI styling and Firebase Hosting deployment

This tutorial will walk you through building a simple CRUD (Create, Read, Update, Delete) application using Vue 3, Pinia for state management, and Firebase Firestore as the backend. This setup is perfect for building modern web apps requiring real-time data syncing, clean architecture, and easy state handling.

Prerequisites

Before we begin, make sure you have the following:

  • Node.js and npm installed
  • A Firebase account
  • Basic knowledge of Vue 3 and Firebase
  • Vue CLI or Vite installed globally (npm install -g create-vite)


1. Project Setup

First, create a new Vue 3 project using Vite:

npm create vite@latest vue3-pinia-firebase-crud -- --template vue
cd vue3-pinia-firebase-crud
npm install
npm run dev

Go to http://localhost:5173/ on your browser.

Vue 3 + Pinia + Firebase CRUD App Tutorial - home app

Press Ctrl + C to stop the running Vue application. Install the required dependencies:

npm install firebase pinia

Now, set up Pinia in your main entry file:

import { createApp } from "vue";
import "./style.css";
import { createPinia } from "pinia";
import App from "./App.vue";

const app = createApp(App);
app.use(createPinia());
app.mount("#app");


2. Firebase Configuration

1. Go to Firebase Console and create a new project.

2. Enable Firestore Database in the Build section -> Firebase Database -> Create Database -> choose Test Mode.

Vue 3 + Pinia + Firebase CRUD App Tutorial - firestore dashboard

3. Click on the Web icon (</>), register your app, and copy the Firebase configuration.

Create a new file, src/firebase.js, for Firebase config:

import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
}

const app = initializeApp(firebaseConfig)
const db = getFirestore(app)

export { db }


3. Creating a Pinia Store

Create a new folder src/stores and a file src/stores/todoStore.js for your store:

import { defineStore } from 'pinia'
import { collection, getDocs, addDoc, doc, deleteDoc, updateDoc } from 'firebase/firestore'
import { db } from '../firebase'

export const useTodoStore = defineStore('todo', {
  state: () => ({
    todos: []
  }),
  actions: {
    async fetchTodos() {
      const querySnapshot = await getDocs(collection(db, 'todos'))
      this.todos = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
    },
    async addTodo(todo) {
      await addDoc(collection(db, 'todos'), todo)
      this.fetchTodos()
    },
    async deleteTodo(id) {
      await deleteDoc(doc(db, 'todos', id))
      this.fetchTodos()
    },
    async updateTodo(id, updated) {
      await updateDoc(doc(db, 'todos', id), updated)
      this.fetchTodos()
    }
  }
})


4. Building the UI

Let’s create simple components to handle adding, displaying, and editing todos.

Create a components/AddTodo.vue, then add:

<template>
    <form @submit.prevent="handleAdd" class="add-form">
        <input v-model="text" placeholder="New Todo" class="input" />
        <button type="submit" class="button">Add</button>
    </form>
</template>

<script setup>
import { ref } from 'vue'
import { useTodoStore } from '../stores/todoStore'

const text = ref('')
const store = useTodoStore()

function handleAdd() {
    if (text.value.trim()) {
        store.addTodo({ text: text.value })
        text.value = ''
    }
}
</script>

Create a components/TodoList.vue, then add:

<template>
    <ul class="todo-list">
        <li v-for="todo in store.todos" :key="todo.id" class="todo-item">
            {{ todo.text }}
            <button @click="store.deleteTodo(todo.id)" class="delete-button">Delete</button>
        </li>
    </ul>
</template>

<script setup>
import { onMounted } from 'vue'
import { useTodoStore } from '../stores/todoStore'

const store = useTodoStore()

onMounted(() => {
    store.fetchTodos()
})
</script>

In src/App.vue, replace all:

<template>
  <div class="app-container">
    <h1 class="title">Vue 3 + Pinia + Firebase Todo App</h1>
    <AddTodo />
    <TodoList />
  </div>
</template>

<script setup>
import AddTodo from './components/AddTodo.vue'
import TodoList from './components/TodoList.vue'
</script>

In src/style.css, replace with:

body {
  font-family: Arial, sans-serif;
  background: #f4f4f4;
  margin: 0;
  padding: 2rem;
}

.app-container {
  max-width: 600px;
  margin: auto;
  background: white;
  padding: 2rem;
  border-radius: 10px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.title {
  text-align: center;
  margin-bottom: 1.5rem;
}

.add-form {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 5px;
}

.button {
  background-color: #42b983;
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 5px;
  cursor: pointer;
}

.button:hover {
  background-color: #369a6e;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-item {
  display: flex;
  justify-content: space-between;
  background: #fafafa;
  padding: 0.75rem 1rem;
  border: 1px solid #ddd;
  border-radius: 5px;
  margin-bottom: 0.5rem;
}

.delete-button {
  background-color: #e74c3c;
  color: white;
  border: none;
  padding: 0.3rem 0.7rem;
  border-radius: 4px;
  cursor: pointer;
}

.delete-button:hover {
  background-color: #c0392b;
}

Now, the Vue application looks like this.

Vue 3 + Pinia + Firebase CRUD App Tutorial - test app


5. Deploying to Firebase Hosting (Optional)

To deploy your Vue app to Firebase Hosting:

npm run build
firebase login
firebase init

Select Hosting and choose `dist` as the public directory.

firebase deploy

Conclusion

In this tutorial, you learned how to build a basic CRUD application using Vue 3, Pinia, and Firebase Firestore. This setup is perfect for small to medium-scale apps and can be easily extended with Firebase Authentication, Storage, and more advanced features.

You can find the full source code on our GitHub.

That's just the basics. If you need more deep learning about MEVN Stack, Vue.js,s or related, you can take the following cheap course:

Thanks!