mongoDB: Banco de dados em tempo real com Node.js

Atualizações em tempo real com Change Streams API

Image for post
Image for post

Você já se viu naquele cenário em que você quer atualizar a sua interface o mais rápido possível após mudanças no seu banco de dados mongo? Por exemplo, um novo usuário é adicionado e você quer atualizar a interface sem precisar fazer uma chamada para sua API ou fazer um constante polling. Escutei um sim? Então esse artigo é para você!

Existem ótimas soluções para esse cenário, principalmente banco de dados criados para atualizações em tempo real, como Firebase e RethinkDB, a verdade é que você também pode criar a mesma solução usando mongoDB e a implementação é bem simples! mongoDB tem uma API chamada Change Streams, que permite adicionar ganchos ao seu banco de dados.

O método que vou mostrar utiliza replicaSets, você também criar a mesma solução em Sharded Clusters (para saber mais sobre ambos, clique nos links disponíveis).

Iniciando mongoDB em modo réplica

Nossa primeira etapa é converter o modo padrão do mongoDB para utilizar um conjunto de réplicas (replica sets).

Precisamos disso porque Change Streams não está disponível no modo padrão do mongoDB. Pode soar mais complexo, mas confie em mim, é bem simples!

Tenha certeza de ter parado todas as instâncias do mongoDB na sua máquina e após isso, vamos rodar:

$ mongod --dbpath=/usr/local/var/mongodb --replSet rs0

O comando acima irá iniciar uma instância do mongo como uma réplica chamada rs0. O caminho usado em --dbpath é o padrão para macOS quando instalamos mongoDB com Homebrew. Você pode especificar o caminho desejado, não é necessário utilizar o mesmo acima.

Agora que nossa instância está rodando em modo réplica, antes de criar um novo banco de dados, nós precisamos iniciar o nosso conjunto de réplicas. Para fazer isso, abra uma nova aba no seu terminal e conecte ao mongo shell.

$ mongo
$ rs.initiate()

Agora, vamos criar um banco de dados chamado UserDB:

$ use UserDB

E vamos inserir dois usuários:

$ db.users.insertMany([  
{ name: "Bebeto" },
{ name: "Ana Paulo" }
])

Com isso, criamos um banco de dados chamado UserDB e uma collection chamada users.

Tudo pronto do lado do nosso banco de dados. Vamos criar uma pequena aplicação para testar nossa stream API.

Uma aplicação de exemplo

Vamos criar uma aplicação para testar o modo réplica com tempo real do mongoDB:

$ mkdir mongo-replica
$ cd mongo-replica
$ yarn add socket.io mongodb mongoose
$ touch {client,models,server}.js

Modelo User do mongoose

Em models.js vamos adicionar:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const UserSchema = new Schema({
name: {
type: String,
required: [true, "Need User name"]
}
});

const User = mongoose.model("User", UserSchema);

module.exports = User;

Servidor com Socket.io para conexão em tempo real

Atualize seu server.js para:

const http = require("http");
const serverSocketIO = require("socket.io");
const mongoose = require("mongoose");

//
// default env vars
//
const {
// **[A]**
MONGODB_URL = "mongodb://localhost:27017/UserDB?replicaSet=rs0",
SERVER_HOST = "localhost",
SERVER_PORT = 3000
} = process.env;

//
// mongoose models
//
const User = require("./models");

//
// http server and socket.io socket
//
const server = http.createServer((req, res) => {
res.end("Hello World!");
});
const serverIO = serverSocketIO(server); // **[B]**

//
// starting server and db conn
//
mongoose.connect(
MONGODB_URL,
{ useNewUrlParser: true },
err => {
if (err) {
console.log(`[SERVER_ERROR] MongoDB Connection:`, err);
process.exit(1);
}

// **[C]**
User.watch().on("change", change => {
console.log(`[SERVER_CHANGE_STREAM] User:`, change);
serverIO.emit("changeData", change);
});

serverIO.on("connection", function() {
console.log("[SERVER_SOCKET_IO] New Connection:", client.id);
});

server.listen(SERVER_PORT, SERVER_HOST, () => {
console.log(`[SERVER] Running at ${SERVER_HOST}:${SERVER_PORT}`);
});
}
);

Tem bastante coisa acontecendo aqui, mas vamos aos pontos imporantes:

  • [A]: Estamos usando a URL padrão do mongoDB, utilizando a porta 27017, o banco que criamos anteriormente UserDB e o mais importante nessa URL é que estamos mencionando a réplica explicitamente.
  • [B]: Criamos um servidor http e passamos para o socket.io, no final do script, podemos ver que estamos escutando na porta 3000 e no host localhost, com isso, podemos testar nossa Stream API utilizando Socket.io
  • [C]: Estamos utilizando a API .watch() do mongoose que é um pequeno wrapper em cima da Change Streams API do driver do mongoDB. Adicionamos o evento .on("change", ... do stream criado no model User. Após qualquer operação na collection UserDB.users, esse callback será chamado, passando o objeto change que contém o documento alterado e metadata sobre a collection, operação realizada, etc.

Dica: Não coloque NENHUMA Change Stream dentro de callbacks do Socket.io, como io.on("connection", ... etc. O uso de CPU por causa disso irá criar um picos, chegando a 95% ou mais.

Criando um cliente simples para testar o Socket.io e Change Stream

Para testar tudo isso, vamos adicionar ao nosso client.js o seguinte:

const io = require('socket.io-client');

const socket = io.connect(`http://localhost:3000`); // **[A]**

socket.on("connect", () => {
console.log(`[CLIENT_SOCKET_IO] Connected:`, socket.connected)
})

socket.on("changeData", payload => { // **[B]**
console.log(`[CLIENT_CHANGE_STREAM] User:`, payload)
})
  • [A]: Uma conexão simulando um cliente para nosso servidor com Socket.io
  • [B]: Relacionado ao item [D] no nosso server.js, quando for emitido o evento changeDate nós estamos escutando o evento no lado do cliente e podemos reagir conforme necessário.

Testando tudo junto

Você vai precisar de 3 abas no seu terminal:

  • Uma para o MongoDB, criado na primeira sessão desse artigo “Iniciando MongoDB em modo réplica”
  • Uma para rodar o server.js com node server.js
  • Uma para rodar o client.js com node client.js

Ficando com algo como:

Aba MongoDB:

Image for post
Image for post

Aba server.js:

Image for post
Image for post

Aba client.js:

Image for post
Image for post

Agora, faça uma alteração na sua collection em UserDB.users, pode ser através do mongo shell ou de algum aplicativo como MongoDB Compass ou Robot 3T. No meu caso, irei abrir outra aba no terminal, conectar no mongo shell e adicionar um novo documento na minha collection:

$ mongo
$ use UserDB
$ db.users.insert({ name: "André" })

Ao fazer isso, perceba que a aba em server.js e client.js foram atualizadas. Isso foi o callback do nosso User.watch().on("change", ... que foi acionado e também o evento serverIO.emit("changeData", ... que enviou um evento para todos os sockets conectados, no nosso caso, o client.js recebeu o payload em socket.on("changeData", payload => ....

Parecido com:

Aba server.js:

Image for post
Image for post

Aba client.js:

Image for post
Image for post

Como resultado, a sua instância do MongoDB está atuando como um banco de dados em tempo real!!1 🎉 🎉🎉

Finalizando

Só falta iniciar um projeto front-end e despachar suas atualizações em tempo real para seu usuário. Isso tudo funciona a partir do MongoDB 3.6, agora você criar melhores experiências para seus usuários sem a necessidade de mudar sua stack ou infraestrutura.

Já utilizou essa API? Tem alguma dia? Compartilha nos comentários! 👋

⭐️ Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store