Criando um chat em 25 minutos com NuxtJS + Vuetify + Firebase - Igor Jacaúna
Comunicação entre componentes
Criando um chat em 25 minutos com NuxtJS + Vuetify + Firebase
As diferentes rotas no Nuxt
Construindo uma aplicação full-stack com NuxtJS e Firestore DB
Um narrador de textos
Usando instância $axios do NuxtJS com Vue-mc
Usando NuxtJS com Firebase
Porque uso NuxtJS
Métodos customizados para Vue-mc
Uma diretiva de permissão
WSL2 - Movendo docker-desktop-data para outro lugar
NuxtJS como já sabemos é um framework incrível feito com VueJS. Ele facilita muito o trabalho e o objetivo dele é de trabalharmos o máximo nos arquivos *.vue
Vamos aprender agora como construir uma aplicação de chat em tempo real com ele, mais o framework de UI Vuetify e o Firebase com seu Realtime Database.
Inicialmente, nosso package.json começa com:
"@nuxtjs/firebase": "^7.4.1",
"firebase": "^8.2.6",
"nuxt": "^2.14.12"
"@nuxtjs/vuetify": "^1.11.3"
Temos então as dependências:
@nuxtjs/firebase - Módulo do Nuxt para ajudar na integração com os serviços do Firebase
firebase - O Firebase em si, que é usado pelo @nuxtjs/firebase
nuxt - Não preciso dizer nada né?
@nuxtjs/vuetify - Por último mas não menos importante, nosso Framework de UI Vuetify. Lembrando que com NuxtJS você pode usar qualquer framework de UI, o npx create-nuxt-app <appname> te dá um lista de opções.
Roda aí o npm ci pra instalar as dependências.
Porque ci e não install? Porque o ci instala a partir do package-lock.json. Isso evita que o npm tente atualizar os pacotes definidos no package.json. É útil para builds automáticos em Integração Contínua. Fica a dica.
Bem, como é pra ser simples, vamos usar apenas o comando nuxt por enquanto. Pra saber dos outros comandos leia aqui.
O Nuxt usa um arquivo onde definimos algumas configurações para ele. Ele funciona sem esse arquivo, mas muito provavelmente você terá que utilizar.
ssr: false, // Não será Server Side Render
target: 'static',
'@nuxtjs/firebase'
initialize: {
onAuthStateChangedMutation: 'auth/SET_USER'
Sobre porque temos ssr: false só checar aqui
Não esqueça de criar um banco de dados no Firebase após criar seu projeto lá. E preencha o databaseURL após criar o banco. As configurações do Firebase são obrigatórias para o módulo @nuxtjs/firebase. Por motivos óbvios né.
modules e buildModules
Nem todo módulo do Nuxt precisa ser executado em runtime pela aplicação, alguns bastam ser executados no build. Temos nesse caso o módulo @nuxtjs/vuetify. Já o módulo @nuxtjs/firebase ele é executado em runtime, pois ele inicializa o Firebase para ser utilizado pela nossa aplicação.
No nosso arquivo nuxt.config.js, criamos uma seção firebase onde definimos algumas configurações para o módulo @nuxtjs/firebase.
database: true,
Observe que além do config, que é onde definimos as configurações de conexão com Firebase. temos também a services, que definimos quais serviços vão ser inicializados pelo módulo. No caso vamos usar o database e o auth. Pra ativar um serviço basta passar true como foi no caso do database, mas se precisar de alguma configuração extra, podemos incluir como foi o caso do auth.
Com onAuthStateChangedMutation dizemos para o módulo chamar uma mutation em caso de alteração do auth no Firebase. Assim, não precisamos configurar nada diretamente, o módulo faz pra gente. É possível usar uma action também. Veja aqui como.
Pra saber todos os serviços só ver aqui.
Pois bem, módulos configurados. Vamos para nossos layouts. Vamos criar 2 layouts. Um será o padrão e outro usado apenas na página de login.
Esse será o layout padrão:
<v-app-bar app>
<img :src="$store.state.auth.user.photoURL" />
<v-btn text @click="logout()">Sair</v-btn>
<v-main app>
this.$fire.auth.signOut()
Observe que já estamos usando a store com $store.state.auth.user.photoURL. Mas não se preocupe que vamos definir isso mais tarde.
Observe também que usamos o $router. O Nuxt já vem integrado com o Vue Router.
A props app passada para v-app-bar e v-main servem para o Vuetify identificar componentes do layout e manipular corretamente. Veja mais detalhes aqui
O componente <nuxt /> é responsável por indicar no nosso layout, onde as rotas serão renderizadas.
Para o nosso layout de login:
Agora vamos para as nossas páginas ou rotas se preferir chamar assim.
A primeira será a nossa home que na verdade será a página de login. Então criamos o diretório pages e dentro dele o arquivo index.vue.
<v-container fill-height>
<v-btn color="primary" x-large dark @click="login"> Login </v-btn>
const provider = new this.$fireModule.auth.GoogleAuthProvider();
await this.$fire.auth.signInWithPopup(provider);
'$store.state.auth.user'() {
if (this.$store.state.auth.user) {
this.$router.replace('/chats');
A página contém só um botão pra fazer login. E temos um watch para quando o usuário estiver logado, substituir a página atual pela página que lista os chats.
E claro, temos também o beforeMount para quando estiver acessando ou dando refresh na página na rota / e já exista usuário logado, substituímos a página com a página de chats. Isso é só pra evitar um usuário logado de acessar a página de login, já que isso não faria sentido.
$fire vs $fireModule
Existem maneiras diferentes de acessar certos recursos do Firebase pelo módulo @nuxtjs/firebase. Pra saber qual recurso usar de acordo com a documentação do Firebase, só dar uma olhada aqui
Observe que definimos explicitamente o layout para a página do login com layout: 'login'. Para as demais páginas o Nuxt vai usar o layout default.
Essa página vai listar os chats criados e também dar a possibilidade de criar um novo chat.
pages/chats.vue
<v-row no-gutters>
<v-text-field v-model.trim="chatName" hide-details outlined class="mr-2"></v-text-field>
<v-btn color="primary" x-large :disabled="chatName === ''" @click="newChat">Novo chat</v-btn>
<v-list-item v-for="(chat, index) of chats" :key="index" :to="`/chat/${chat.id}`">
const chats = this.$fire.database.ref('chats')
chats.on('child_added', snapshot => {
this.chats.push({
...snapshot.val()
chats.off()
chats: [],
chatName: ''
newChat() {
const ref = this.$fire.database.ref('chats').push()
name: this.chatName
this.chatName = ''
Uma das coisas legais que o Vue tem é o modificador trim nos v-model. Isso faz com que automaticamente o Vue já faça o trim do input, removendo os espaços nas bordas da string. Ajuda com o problema de submeter strings vazias através de um input. Existe também o modificador number que faz com que '4' se torne um 4. Dá uma olhada aqui pra saber mais.
Isso é um hook do Nuxt, ele ajuda a definir ações de fetch para todo e qualquer componente, seja página ou não. Ele cria indicadores do estado, quando retornamos uma promise no fetch, com $fetchState como $fetchState.pending ou $fetchState.error pra ajudar a criar loadings ou mensagens de erro. Pra saber mais só olhar aqui
$on('hook:beforeDestroy')
No caso do Firebase, é necessário "desligar" certos listeners quando saímos de uma página caso contrário, continuarão funcionando. Pra evitar salvar uma instância de um ref no data e aumentar o consumo de memória pois o Vue acabaria por tornar reativo o ref, podemos usar o hook:beforeDestroy. É o mesmo que usarmos o beforeDestroy() do componente, com a diferença de que não vou precisar usar o data() pra salvar uma referência de algo pra usar no beforeDestroy() do componente.
A nossa última página, o chat em si. Pra entender o nome do arquivo, sugiro ler esse artigo
pages/chat/_id.vue
<v-toolbar color="primary" dark>
<v-btn icon @click="$router.back()">
<v-icon> mdi-chevron-left </v-icon>
<v-list max-height="400px" min-height="400px">
<v-list-item-group>
<v-list-item v-for="(message, index) of messages" :key="index">
<v-avatar color="secondary" size="32" class="mr-2">
<img :src="message.photo" v-if="message.photo" />
<span v-else>{{ message.author[0] }}</span>
<b>{{ message.author }}:</b> {{ message.message }}
<v-divider> </v-divider>
<form @submit.prevent="sendMessage">
<v-card-actions class="pa-5">
v-model.trim="message"
placeholder="Digite sua mensagem"
:disabled="message === ''"
>Enviar</v-btn
this.room = this.$fire.database.ref(
`/chats/${this.$route.params.id}/messages`
this.room.on("child_added", (snapshot) => {
this.messages.unshift(snapshot.val());
this.$on("hook:beforeDestroy", () => {
this.room.off();
this.room.push({
author: this.$store.state.auth.user.displayName,
photo: this.$store.state.auth.user.photoURL,
Usamos um <form> porque é mais conveniente apertar <ENTER> pra enviar mensagem. E usamos o modificador .prevent no @submit pra obviamente prevenir o comportamento padrão do <form> quando ele é submetido.
Pois bem, construídas as páginas, vamos ao nosso middleware. Ele será responsável por permitir ou não o acesso a uma página onde fica definido o seu uso.
middleware/auth.js
export default ({ store, redirect }) => {
if (!store.state.auth.user) {
Ele é bem simples. Um middleware no Nuxt recebe um contexto como parâmetro, nós descontruímos esse contexto para só trabalhar com o store do Vuex e com a função de redirect.
Agora nas nossas páginas, você deve ter percebido que definimos o uso desse nosso middleware apenas passando: middleware: 'auth':
Por último, vamos criar nosso store do Vuex. Não há nada complicado aqui, basta criar um arquivo auth.js dentro do diretório store que o Nuxt passará a usar o Vuex e já criará um módulo Vuex com namespace auth (que é o mesmo nome do arquivo)
store/auth.js
SET_USER(state, { authUser }) {
if (authUser) {
const { displayName, photoURL } = authUser
No nosso módulo, definimos o state e uma mutation.
Essa mutation é usada pelo @nuxtjs/firebase lembra? Configuramos ele pra usar essa mutation quando houver alteração no auth do firebase.