Vue + Firebase: A Full-Stack Frontend Survey Flow

Firebase short-circuits the traditional split between frontend and backend code. Instead of writing and hosting your own API, Firebase exposes ready-made endpoints for authentication, databases, file storage, and more — which means a frontend developer can build what is effectively a full-stack application using only client-side code. In this guide, we use Vue with Firebase to walk through a realistic survey feature: validating user input, registering and logging in users, protecting routes, and persisting survey responses to a secure Realtime Database.

Project Configuration and Firebase Setup

You’ll need Node and npm or yarn installed, plus a working knowledge of Vue, Vuex, and Vue Router syntax. To follow along, clone the project starter files from GitHub and run npm install. The starter includes a welcome page with sign-up and sign-in options; the survey becomes visible only after login. If you build from scratch, install Vuex, Vue Router, Vuelidate, and axios into the project.

Start by creating a Firebase project — essentially a container granting access to authentication, database, and hosting services. Inside the Firebase console, configure the authentication system:

  • Click “Authentication” and set a sign-in method; for this project, choose email/password.
  • Click “Database”, then choose “Realtime database”. Copy the database URL at the top — this will be the endpoint for sending survey data, referred to below as the database API.

To write data, you must then append a database name and .json to that URL. For example, sending to a database node named user means targeting user.json at the end of the API endpoint.

For authentication requests, consult the Firebase Auth REST API docs and extract the sign-up and sign-in endpoints. Each request requires your project’s API key, available under project settings.

Form Validation with Vuelidate

Before a user’s sign-up information ever hits the server, it should check out client-side. Vuelidate handles this cleanly in a Vue component. Install it, then open src/components/auth/signup.vue and import the necessary validators from the library, plus axios for HTTP requests:

Database rules also play a role in validation. Set your Realtime Database security rules so that reads are permitted without authentication, but writes require an authenticated user. Additionally, index emails so you can query the users node and filter results by a unique email address using Firebase’s built-in query parameters.

Back in the Vue component, add a custom computed property named validations. Define rules for each field:

  • Email: required, must pass the email format check. An asynchronous custom validator named unique queries the database with axios as the user types; if the returned Object.keys length is 0, the email is available.
  • Age: required, numeric, with a minimum value of 18 — exposed via a property like minVal.
  • Password: required, with a minimum length of 6 via minLen.
  • Confirm password: must match the original password value.

In the template, use v-if statements to communicate validation states — e.g., show “email taken” when unique returns false. Mark surrounding div elements with an invalid class when an input has errors. Use Vuelidate’s $touch() to bind validation events to each input, checked on blur. Disable the submit button until all fields validate. Age, password, and confirm password get bound the same way as the email input.

Authentication and User State

Since this is a single-page app, Vuex acts as a central store to broadcast login status across components. In the store file, define both sign-in and sign-up actions under actions. Authentication responses (a token and userId) belong in Vuex state — the token indicates whether the user remains logged in anywhere in the app. Initialize token, userId, and a user object in state as null.

Mutations update that state; the authUser mutation stores the token and user ID. In the sign-up action, handle a dual-write pattern. The first request registers only email and password with Firebase’s authentication database — a store you can’t read from or write to directly. In a separate action named storeUser, send the complete sign-up data (email, age, survey answers, etc.) to your own Realtime Database node. Dispatch this action automatically after successful sign-up.

Be mindful not to pass raw password data into storeUser, as you have full read/write access to this database and might expose sensitive credentials. The storeUser action includes the database API URL, the newly obtained token for authentication, and posts to a user node. Remember: Firebase only accepts writes for authenticated users per the rules you configured upfront.

Components call these actions from an onSubmit method, passing the form data payload.

Route Guarding and Logout

Protect the survey dashboard so unauthenticated visitors can’t reach it. In the router file, import the Vuex store. For the dashboard route, add a navigation guard that checks whether a token exists in the store; if yes, let the user through, otherwise redirect them away.

Logout completes the flow. The store gains a logout action that commits to a clearAuth mutation — resetting token and userId to null — then clears local storage and calls router.replace('/') to send the user back to the landing page. In the header component, fire this action from an onLogout method bound to a button’s @click event.

Conditional Navigation Visibility

Finally, to keep the dashboard link hidden from logged-out users, add a getter called ifAuthenticated that checks whether the token in state is non-null. In the header component’s computed section, map that getter into an auth method; it returns true when logged in, null otherwise. Add a v-if conditional in the template on the survey navigation item — the link appears only when auth evaluates truthy.

All the pieces together form a functional survey app, but the patterns — validated forms, Vuex-driven auth state, guarded routes, and Firebase endpooints — apply directly to any future Vue project, even one backed by a custom API.

Keeping Users Signed In Across Reloads

Because the Firebase token and user ID live in Vuex state, a page refresh wipes them out and signs the user out. The fix is to persist that data in local storage and restore it when the app boots. An autoLogin action reads the token and userId from local storage and commits them through the existing authUser mutation.

actions : {
  AutoLogin ({commit}) {
      const token = localStorage.getItem('token')
      if (!token) {
        return
      }
      const userId = localStorage.getItem('userId')
      const token = localStorage.getItem('token')
      commit('authUser', {
        idToken: token,
        userId: userId
      })
  }
}

In App.vue, a created hook dispatches autoLogin every time the application loads, so the session is restored automatically as long as the token is still valid.

created () {
    this.$store.dispatch('AutoLogin')
  }

Loading the User's Profile

To greet the user on the dashboard, the app needs the name stored at sign-up. A new fetchUser action first verifies that a token exists, then pulls the email from local storage and queries the database with that email, just like the sign-up validation flow does. The response contains the user object submitted during sign-up; that object is converted into an array and committed to the existing storeUser mutation.

fetchUser ({ commit, state}) {
  if (!state.idToken) {
    return
  }
  const email = localStorage.getItem('email')
  axios.get('https://vue-journal.firebaseio.com/users.json?orderBy="email"&equalTo="' + email + '"')
    .then(res => {
      console.log(res)
    
     // const users = [] 
      console.log(res.data)
      const data = res.data
      const users = []
      for (let key in data) {
        const user = data[key]
        user.id = key
        users.push(user)
        console.log(users)
      }
     commit('storeUser', users[0])
    })
    .catch(error => console.log(error))
}

A new getter named user exposes that stored data from state.user.

getters: {
  user (state) {
    return state.user
  },
  isAuthenticated (state) {
    return state.idToken !== null
  }
}

On the dashboard, a computed property name returns state.user.name only when the user object exists, and a created hook dispatches the fetchUser action on page load. The template uses v-if so the greeting only renders when the name is present.

computed: {
  name () {
      return !this.$store.getters.user ? false : this.$store.getters.user.name
    }
  },
  created () {
    this.$store.dispatch('fetchUser')
  }
}
 <p v-if="name">Welcome, {{ name }} </p>

Submitting Survey Responses

A postData action handles sending survey responses to the Firebase database. It uses the database API and includes the token so Firebase recognizes the authenticated user.

postData ({state}, surveyData) {
  if (!state.idToken) {
    return
  }
  axios.post('https://vue-journal.firebaseio.com/survey.json' + '?auth=' + state.idToken , surveyData)
    .then(res => {
     console.log(res)
    })
    .catch(error => console.log(error))
}

The dashboard component dispatches its survey data to this action in the store, completing the loop between the UI, Vuex, and Firebase.

methods : {
  onSubmit () {
    const postData = {
      price: this.price,
      long: this.long,
      comment: this.comment
    }
    console.log(postData)
    this.$store.dispatch('postData', postData)
  }
}

These features—authentication, session persistence, profile loading, and database writes—cover the core pieces you'll need when building modern web apps with Vue and Firebase. The live demo is available here.

For deeper background, these resources are worth reading: