Como criar um aplicativo de pesquisa Vue usando autenticação e banco de dados do Firebase

Publicados: 2022-03-10
Resumo rápido ↬ Este tutorial mostra um guia passo a passo para criar um aplicativo de pesquisa funcional usando Vue.js e Firebase. Desde a validação dos dados do usuário através do Vuelidate, até a autenticação, armazenamento dos dados do usuário, proteção de rotas e envio de dados para servidores Firebase. Todos os passos usados ​​no tutorial são práticos e podem ser reproduzidos em qualquer projeto da vida real, mesmo com um backend customizado.

Neste tutorial, você construirá um aplicativo de pesquisa, onde aprenderemos a validar os dados do formulário de nossos usuários, implementar a autenticação em Vue e receber dados de pesquisa usando Vue e Firebase (uma plataforma BaaS).

À medida que construímos este aplicativo, aprenderemos a lidar com a validação de formulários para diferentes tipos de dados, incluindo entrar em contato com o back-end para verificar se um e-mail já foi recebido, mesmo antes de o usuário enviar o formulário durante a inscrição.

Além disso, o aplicativo lidaria com o login do usuário com APIs de descanso. Ele usará o Authguard no roteador Vue para impedir que usuários não logados obtenham acesso ao formulário de pesquisa e enviem com sucesso os dados de pesquisa de usuários logados para um banco de dados seguro.

Para estarmos na mesma página, vamos esclarecer o que é o Firebase e o que ele fará neste tutorial. O Firebase é um conjunto de ferramentas para “criar, melhorar e expandir seu aplicativo”, ele dá acesso a uma grande parte dos serviços que os desenvolvedores normalmente teriam que construir sozinhos, mas não querem construir porque preferem concentre-se na experiência do aplicativo em si. Isso inclui coisas como análise, autenticação, bancos de dados, armazenamento de arquivos e a lista continua.

Isso é diferente do desenvolvimento tradicional de aplicativos, que normalmente envolve escrever software de front-end e back-end. O código de front-end apenas invoca os endpoints da API expostos pelo back-end, e o código de back-end realmente faz o trabalho. No entanto, com os produtos Firebase, o back-end tradicional é ignorado, colocando o trabalho no cliente. Isso tecnicamente permite que engenheiros de front-end como eu construam aplicativos full-stack escrevendo apenas código de front-end.

Mais depois do salto! Continue lendo abaixo ↓

A conclusão é que o Firebase atuaria como nosso back-end neste projeto, fornecendo-nos os endpoints de API necessários para lidar com nossas necessidades de autenticação e banco de dados. No final, você terá construído um aplicativo de pesquisa funcional usando Vue+ Firebase. Depois disso, você pode criar qualquer aplicativo da Web de sua escolha usando esses mesmos processos, mesmo com um back-end personalizado.

Para acompanhar, você precisa ter o Node e o npm/yarn instalados em sua máquina. Se você ainda não fez isso, siga estes guias rápidos para instalar o yarn ou o npm em sua máquina. Você também precisa ter um entendimento básico da sintaxe do roteador Vue, Vuex e Vue para este tutorial.

Os arquivos iniciais para este tutorial estão aqui, que contém os arquivos base para este projeto, e aqui está o repositório para a demonstração concluída. Você pode clonar ou baixar os repositórios e executar npm install em seu terminal.

Depois de instalar o arquivo inicial, você verá uma página de boas-vindas, que tem as opções para se inscrever e fazer login. Depois de fazer login, você poderá acessar a pesquisa.

Arquitetura do aplicativo de pesquisa
Isso descreve como nosso aplicativo de pesquisa funcionará. (Visualização grande)

Sinta-se à vontade para criar um novo projeto se quiser construir este projeto inteiramente por conta própria, apenas certifique-se de instalar Vuex, roteador Vue, Vuelidate e axios em seu projeto Vue. Então vamos pular direto:

Primeiro, precisaremos de uma conta do Firebase para configurar este projeto, que é muito parecido com criar um contêiner para nosso aplicativo, nos dando acesso ao banco de dados, vários meios de autenticação, hospedagem etc. É fácil configurar assim que você estão no site do Firebase.

Página de destino do Firebase
A página de destino onde você pode se inscrever e iniciar sua jornada no Firebase. (Visualização grande)
Criar novos projetos do Firebase
Criação de projetos do Firebase (visualização grande)

Agora que temos nosso projeto, o próximo passo é configurar nosso sistema de autenticação e banco de dados (banco de dados em tempo real) no Firebase.

  • Clique na opção “autenticação”;
  • Configure o “método de login” que queremos (neste caso, email/senha).
Configurar método de login
Configure o método de autenticação de e-mail/senha para o projeto. (Visualização grande)
  • Clique em “banco de dados”.
  • Escolha “Banco de dados em tempo real” e copie este link que está logo acima.

Será muito útil como endpoint da API quando quisermos enviar os dados para nosso banco de dados do Firebase.

Vamos nos referir a essa API como a API do banco de dados. Para usá-lo, você terá que adicionar o nome do banco de dados de sua escolha ao enviá-lo. Por exemplo, para enviar para um banco de dados chamado user. Você simplesmente adiciona user.json no final:

 {databaseAPI}/user.json
Banco de dados em tempo real
Use a API acima do próprio banco de dados para enviar dados ao banco de dados. (Visualização grande)

Depois disso, acessaremos a documentação da API Firebase auth rest para fazer nossa inscrição e fazer login nos endpoints da API. Dentro desses endpoints, haverá a necessidade da chave de API do nosso projeto, que pode ser encontrada nas configurações do nosso projeto.

Validação

Voltando ao nosso código, haverá uma validação dos dados de cadastro antes de serem enviados ao servidor, apenas para garantir que o usuário esteja enviando as informações apropriadas. Estaremos usando o Vuelidate, que é uma biblioteca legal que facilita a validação no Vue. Antes de tudo, instale o Vuelidate no projeto:

 npm i vuelidate

Vá para src/components/auth/signup.vue e dentro da tag script import vuelidate e todos os eventos necessários que precisaremos da biblioteca como visto abaixo.

Observação : você pode verificar os documentos para obter uma visão geral completa da biblioteca e de todos os eventos disponíveis.

 import { required, email, numeric, minValue, minLength, sameAs } from 'vuelidate/lib/validators'

Uma explicação rápida:

Descrição
Valor
required O valor é obrigatório
email O valor deve ser um e-mail
numeric Deve ser um número
minValue Menor valor numérico que o usuário pode inserir.
sameAs Usado para comparar entre dois valores para garantir que eles sejam os mesmos
Importe também [`axios`](https://github.com/axios/axios) para poder enviar uma solicitação HTTP para o servidor:
 import axios from 'axios'
Antes de prosseguirmos, precisaremos adicionar algumas regras ao banco de dados para podermos validar o email como deveríamos, conforme visto abaixo:
Regras do Firebase
As regras do banco de dados ajudam a decidir se você pode ou não acessar o banco de dados a qualquer momento. (Visualização grande)
 "read" = "true"
O que significa que o banco de dados pode ser lido sem qualquer impedimento do lado do cliente.
 "write" = "auth" !== null
Você não pode escrever no banco de dados, exceto se for um usuário autenticado.
 "Users" = { "onIndex" : ["email"] }
Isso nos permite consultar o documento `users` com um índice de `email`. Ou seja, você pode literalmente filtrar o banco de dados para um e-mail exclusivo. Em seguida, adicione uma propriedade computada personalizada com o nome `validations` assim como temos métodos, computados, etc. Em `validations` teremos métodos para validar os dados necessários a partir de `email` onde é obrigatório e obviamente deve ser um email . Além disso, queremos ser capazes de dizer a um usuário quando um email já foi recebido por outra pessoa, verificando o banco de dados após o usuário digitá-lo usando algo chamado validadores assíncronos, tudo dentro de um validador personalizado e tudo suportado por [vuelidate. ](https://vuelidate.js.org/#sub-asynchronous-validation)
 validations : { email: { required, email, unique: val => { if (val === '') return true return axios.get('https://vue-journal.firebaseio.com/users.json?orderBy="email"&equalTo="' + val + '"') .then(res => { return Object.keys(res.data).length === 0 }) } } }
Em seguida, em unique, consulte o banco de dados usando axios e use o Object.keys padrão para retornar a resposta apenas se seu comprimento for 0. Para a idade, você adicionará um valor obrigatório, numérico e um valor mínimo de 18 que é atribuído a `minVal ` como suas propriedades.
 age: { required, numeric, minVal: minValue(18) }
As propriedades da senha são obrigatórias, com um comprimento mínimo de 6 atribuído a `minLen`.
 password: { required, minLen: minLength(6) }
As propriedades `confirmPassword` são basicamente as mesmas que a senha.
 confirmPassword: { sameAs: sameAs(vm => { return vm.password }) }
Para informar ao usuário que o e-mail foi recebido, use `v-if` para verificar se `unique` é verdadeiro ou falso. Se true, significa que o comprimento do objeto retornado é 0, e o email ainda pode ser usado e vice-versa. Da mesma forma, você pode verificar se a entrada do usuário é um e-mail real usando `v-if`. E para todos os divs circundantes na entrada individual, adicionaremos uma classe de invalid que se tornará ativa quando houver um erro nessa entrada. Para vincular os eventos de validação a cada entrada no HTML, usamos [`$touch()`](https://vuelidate.js.org/#sub-without-v-model) como visto com o `email ` abaixo.
 <div class="input" :class="{invalid: $v.email.$error}"> <h6 v-if="!$v.email.email">Please provide a valid email address.</h6> <h6 v-if="!$v.email.unique">This email address has been taken.</h6> <input type="email" placeholder="Email" @blur="$v.email.$touch()" v-model="email"> </div>
`Age`, `password` e `confirmPassword` serão vinculados à sua entrada HTML de maneira semelhante ao `email`. E tornaremos o botão 'Enviar' inativo se houver um erro em qualquer uma das entradas.
 <button type="submit" :disabled="$v.$invalid">create</button>
Aqui está um [exemplo CodePen](https://codepen.io/atanda1/pen/Yzyqrjv) completo para esta seção vuelidate.
Implementação do Vuelidate
Vuelidate é usado aqui para determinar o tipo de dados enviados ao banco de dados. (Visualização grande)
## Autenticação Este aplicativo é um SPA e não recarrega como sites tradicionais, então usaremos Vuex, como nossa única "fonte de verdade" para permitir que todos os componentes em nosso aplicativo estejam cientes do status geral de autenticação. Vamos para o nosso arquivo de armazenamento e criamos o método de login/inscrição nas ações. As respostas (`token` e `userId`) recebidas quando enviamos os dados dos usuários, serão armazenadas dentro do nosso estado. Isso é importante porque o token será usado para saber se ainda estamos logados ou não em algum ponto do nosso aplicativo. O `token`, `userId` e o usuário são criados no estado com um valor inicial nulo. Chegaremos ao usuário muito mais tarde, mas, por enquanto, vamos nos concentrar nos dois primeiros.
 state: { idToken: null, userId: null, user: null }
As mutações são então criadas para alterar o estado quando necessário.
authUser Salva o token e o userId
storeUser Armazena as informações do usuário
clearAuthData Apaga os dados de volta ao estado inicial
 mutations: { authUser (state, userData) { state.idToken = userData.token state.userId = userData.userId }, storeUser (state, user) { state.user = user }, clearAuthData (state) { state.idToken = null state.userId = null state.user = null } }
Para inscrição/login, teremos que criar ações individuais para ambos, onde enviamos nossas solicitações de autenticação para o servidor. Depois disso, nossa resposta (token e userId) de inscrição/login é confirmada para authUser e salva no armazenamento local.
 signup ({commit, dispatch}, authData) { axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyCFr-OMMzDGp4Mmr0t66w2cTGfNazYjptQ', { email: authData.email, password: authData.password, returnSecureToken: true }) .then(res => { console.log(res) commit('authUser', { token: res.data.idToken, userId: res.data.localId }) localStorage.setItem('token', res.data.idToken) localStorage.setItem('userId', res.data.localId) localStorage.setItem('email', res.data.email) dispatch('storeUser', authData) setTimeout(function () { router.push('/dashboard') }, 3000) }) .catch(error => console.log(error)) }
 login ({commit}, authData) { axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyCFr-OMMzDGp4Mmr0t66w2cTGfNazYjptQ', { email: authData.email, password: authData.password, returnSecureToken: true }) .then(res => { console.log(res) localStorage.setItem('token', res.data.idToken) localStorage.setItem('userId', res.data.localId) localStorage.setItem('email', res.data.email) commit('authUser', { token: res.data.idToken, userId: res.data.localId }) router.push('/dashboard') }) .catch(error => console.log(error.message)) }
Mas aqui está a parte complicada, o que faremos com a ação de inscrição em particular é enviar apenas o e-mail e a senha para serem registrados no banco de dados de autenticação. Na verdade, não temos acesso para usar os dados desse banco de dados de autenticação e não enviamos nenhum de nossos dados de inscrição além de e-mail/senha. Então, o que faremos é criar outra ação para enviar os dados de inscrição completos para outro banco de dados. Neste documento de banco de dados separado, teremos acesso completo a todas as informações que escolhermos salvar lá. Chamaremos essa nova ação de `storeUser`. Então vamos para nossa ação de inscrição e despachamos todo o objeto contendo nossos dados de inscrição para um banco de dados ao qual agora temos acesso através de `storeUser`. **Nota:** Você pode não querer enviar a senha do seu usuário com `storeUser` para o banco de dados por motivos de segurança.
 storeUser ({ state}, userData) { if (!state.idToken) { return } axios.post('https://vue-journal.firebaseio.com/users.json' + '?auth=' + state.idToken, userData) .then(res => console.log(res)) .catch(error => console.log(error)) } }
`storeUser` adiciona uma consulta usando nosso token recém-obtido e a API do banco de dados ao postar no banco de dados. Isso ocorre porque não podemos gravar em nosso banco de dados, exceto que somos autenticados com nossa prova (o token). Essa é a regra que demos ao Firebase no início, lembra?
 “write” = “auth” !== null
O código completo para ações de inscrição/entrada está aqui [aqui](https://codepen.io/atanda1/pen/mdePKqj). Em seguida, envie a inscrição e o login de seus componentes no método `onSubmit` para as respectivas ações na loja.
 methods : { onSubmit () { const signupData = { email : this.email, name : this.name, age : this.age, password : this.password, confirmPassword : this.co nfirmPassword } this.$store.dispatch('signup', signupData) } } }
**Observação:** `signupData` contém os dados do formulário.
 methods : { onSubmit = { const formData = { email : this.email, password : this.password } this.$store.dispatch('login', {email: formData.email, password: formData.password}) } }
## AuthGuard É necessário que o AuthGuard impeça que usuários não logados tenham acesso ao painel para onde enviarão a pesquisa. Acesse o arquivo de rotas e importe nossa loja.
 import store from './store'
Dentro da rota, vá para o caminho do painel e adicione o seguinte:
 const routes = [ { path: '/', component: WelcomePage }, { path: '/signup', component: SignupPage }, { path: '/signin', component: SigninPage }, { path: '/dashboard', component: DashboardPage, beforeEnter (to, from, next) { if (store.state.idToken) { next() } else { next('/signin') } } } ]
Tudo o que isso faz é verificar se há um token no estado, se sim, damos acesso ao painel e vice-versa. ## LogOut Para criar nossa opção de logout, usaremos o `clearAuth` que criamos anteriormente em `mutations` que apenas configura o `token` e o `userId` para `null`. Agora criamos um novo `logout` `action` , que se compromete com `clearAuth`, exclui o armazenamento local e adiciona `router.replace('/')` para redirecionar o usuário completamente.
 actions: { logout ({commit}) { commit('clearAuth') localStorage.removeItem('token') localStorage.removeItem('userId') router.replace('/') } }
No componente de cabeçalho, temos um método `onLogout` que despacha nossa ação de logout na loja.
 methods: { onLogout() { this.$store.dispatch('logout') } }
Em seguida, adicionamos um `@click` ao botão que dispara o método `onLogout` como podemos ver [aqui](https://codepen.io/atanda1/pen/jObqKNd).
 <ul @click="onLogout">Log Out</ul>
## UI_State Agora que concedemos acesso condicional ao painel, a próxima etapa é removê-lo da barra de navegação, para que apenas usuários autenticados possam visualizá-lo. Para fazer isso, adicionaríamos um novo método nos `getters` chamado `ifAuthenticated` que verifica se o token dentro do nosso estado é nulo. Quando há um token, ele mostra que o usuário está autenticado e queremos que ele veja a opção do painel de pesquisa na barra de navegação.
 getters: { isAuthenticated (state) { return state.idToken !== null } }
Depois disso, você volta para o componente de cabeçalho e cria um método `auth` em computado, que despacha para nosso `isAuthenticated` dentro dos `getters` que acabamos de criar na loja. O que isso faz é que `isAuthenticated` retornaria false se não houvesse token, o que significa que `auth` também seria nulo e vice-versa.
 computed: { auth () { return this.$store.getters.ifAuthenticated } }
Depois disso, adicionamos um `v-if` ao nosso HTML para verificar se `auth` é nulo ou não, determinando se essa opção seria exibida na barra de navegação.
 <li v-if='auth'> <router-link to="/dashboard">Dashboard</router-link> </li> <li v-if='!auth'> <router-link to="/signup">Register</router-link> </li> <li v-if='!auth'> <router-link to="/signin">Log In</router-link> </li>
- Você encontrará o código completo da seção UI State [aqui](https://codepen.io/atanda1/pen/QWjNxyo).
Estado da IU
Há alteração no cabeçalho com base no status de autenticação do usuário. (Visualização grande)

Login automático

Quando recarregamos nosso aplicativo, perdemos os dados e somos desconectados, tendo que começar tudo de novo. Isso ocorre porque nosso token e Id são armazenados em Vuex, que é javascript, e isso significa que nosso aplicativo é recarregado com o navegador quando atualizado.

E então, finalmente, o que faremos é recuperar o token em nosso armazenamento local. Ao fazer isso, podemos ter o token do usuário no navegador, independentemente de quando atualizarmos a janela, e ter um método de login automático de nosso usuário, desde que o token ainda seja válido.

Um novo método de actions chamado AutoLogin é criado, onde obteremos o token e o userId do armazenamento local e enviaremos nossos dados para o método authUser nas mutações.

 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 }) } }

Em seguida, vamos ao nosso App.vue e escrevemos um método created , que despachará o autoLogin de nossa loja toda vez que o aplicativo for carregado.

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

Buscar_User_Data

Queremos dar as boas-vindas ao usuário no painel exibindo o nome do usuário. E assim, outra ação chamada fetchUser é criada, que primeiro verifica se há um token como de costume. Em seguida, ele obtém o email do armazenamento local e consulta o banco de dados conforme feito anteriormente com a validação do email.

Isso retorna um objeto contendo os dados do usuário inicialmente enviados durante a inscrição. Em seguida, convertemos esse objeto em um array e o comprometemos com a mutação storeUser inicialmente criada.

 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)) }

Depois disso, criamos outro getter chamado user que retorna o state.user já confirmado por meio de storeUser .

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

De volta ao painel, criamos um novo método computado chamado name que retorna state.user.name somente se o usuário existir.

 computed: { name () { return !this.$store.getters.user ? false : this.$store.getters.user.name } }, created () { this.$store.dispatch('fetchUser') } }

E também adicionaremos a propriedade computada created para despachar a ação fetchUser assim que a página for carregada. Em seguida, usamos o v-if em nosso HTML para exibir o nome se o nome existir.

 <p v-if="name">Welcome, {{ name }} </p>

Send_Survey

Para enviar a pesquisa, criaremos uma ação postData que envia os dados para o banco de dados usando a API do banco de dados, com o token para mostrar ao servidor que o usuário está logado.

 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)) }

Voltamos ao componente do painel e despachamos os dados para nossa ação postData na loja.

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

Aí está, temos muitos recursos úteis implementados em nosso aplicativo de demonstração enquanto nos comunicamos com nosso servidor Firebase. Espero que você use esses recursos poderosos em seu próximo projeto, pois eles são muito críticos para a criação de aplicativos da Web modernos hoje.

Se você tiver alguma dúvida, pode deixá-las na seção de comentários e terei prazer em responder a cada uma delas!

  • A demonstração do tutorial está disponível aqui.
Aplicativo de pesquisa Vue
O aplicativo de pesquisa concluído (visualização grande)

Outros recursos que podem ser úteis incluem:

  • Para entender mais sobre o Firebase e os outros serviços que ele oferece, confira o artigo de Chris Esplin, “O que é Firebase?”
  • Vuelidate é uma biblioteca muito legal na qual você deveria se aprofundar. Você deve ler sua documentação para obter uma visão completa.https://vuelidate.js.org/.
  • Você também pode explorar axios por conta própria, especialmente se quiser usá-lo em projetos maiores.