Conectando Node.js ao MongoDB com Mongoose

Rocketseat

Navegação Rápida:
Se você desenvolve com Node.js, sabe que a flexibilidade do MongoDB é uma grande vantagem. Contudo, gerenciar dados de forma consistente nesse ambiente pode se tornar um desafio. É aqui que entra o Mongoose, uma ferramenta que serve como ponte entre seu código e o banco de dados, equilibrando a liberdade do NoSQL com a estrutura e a previsibilidade que aplicações robustas exigem.
Se você caiu aqui de paraquedas e quer um vídeo de como sair do zero em node.js, eu tenho a recomendação certa para você!

Por que Mongoose?
O Mongoose é uma biblioteca de Object Data Modeling (ODM). Ele converte os objetos JavaScript, com os quais você já trabalha no Node.js, para o formato de documentos que o MongoDB entende, e faz o caminho inverso quando você busca informações. A própria documentação descreve o Mongoose como uma forma de modelagem de objetos elegante para Node.js.
Embora seja possível trabalhar com o MongoDB sem um ODM, o valor do Mongoose está na simplificação da modelagem de dados, na aplicação de validações e na manipulação geral das informações. O verdadeiro ganho está na melhoria da developer experience (experiência do desenvolvedor). Ele permite que a pessoa que programa pense em termos de objetos e classes, um paradigma mais intuitivo do que construir queries manualmente. A interação com o banco de dados, por meio de comandos como
new User()
ou user.save()
, passa a usar a mesma sintaxe e lógica do restante da aplicação, diminuindo a carga cognitiva e acelerando o desenvolvimento.Schemas e Models
Schema: é a planta baixa, o contrato que dita a estrutura dos documentos dentro de uma collection. É no Schema que você define os campos, seus tipos de dados (como
String
, Number
, Date
, Boolean
, Array
e outros) e as regras que eles devem seguir.Veja um exemplo de um Schema para um post de blog:
import mongoose from 'mongoose'; const { Schema } = mongoose; const postSchema = new Schema({ title: String, author: String, body: String, published: Boolean, meta: { votes: Number, favs: Number }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } });
Model: é a interface que permite a interação direta com a collection do banco de dados. É por meio do Model que as operações de Criar, Ler, Atualizar e Deletar (CRUD) são executadas. Uma convenção importante do Mongoose é que, ao chamar
mongoose.model('Post', postSchema)
, a biblioteca automaticamente procura pela collection posts
(o nome do modelo no plural e em minúsculas) no banco de dados.Preparando o ambiente
Esta seção é um guia para configurar um projeto Node.js, deixando-o pronto para a conexão com o banco de dados.
Iniciando o projeto e instalando as dependências:
No seu terminal, execute os seguintes comandos para criar um diretório para o projeto, inicializá-lo e instalar as dependências:
mkdir api-mongodb-rocketseat cd api-mongodb-rocketseat npm init -y npm install mongoose dotenv
Instalamos dois pacotes:
mongoose
, para a interação com o MongoDB, e dotenv
, para o gerenciamento de variáveis de ambiente. Para usar uma sintaxe mais moderna, adicione a seguinte linha ao seu package.json
. Isso habilita o uso de ES Modules (import
/export
), melhorando a organização do código.A importância das variáveis de ambiente com.env:
Expor credenciais, como a string de conexão do banco, diretamente no código é um risco de segurança. A solução é usar variáveis de ambiente. Crie um arquivo chamado
.env
na raiz do seu projeto e adicione a sua string de conexão:#.env MONGO_URI=SUA_STRING_AQUI
O uso de um arquivo
.env
vai além da segurança, pois promove portabilidade e flexibilidade. Sua aplicação pode se conectar a diferentes bancos (desenvolvimento, testes, produção) apenas alterando o valor dessa variável, sem precisar modificar o código.Configurando o MongoDB Atlas ou banco local:
Para obter uma string de conexão, você pode usar o MongoDB Atlas, um serviço de banco de dados na nuvem. O processo envolve criar um cluster gratuito, configurar um usuário e senha para o banco e liberar o acesso por IP.
Como alternativa, para um banco de dados rodando localmente, a string de conexão seria:
mongodb://127.0.0.1:27017/nome-do-banco
É recomendado usar 127.0.0.1 em vez de localhost. Versões recentes do Node.js podem tentar resolver localhost para um endereço IPv6, o que pode causar falhas de conexão se o seu MongoDB não estiver configurado para isso.
A string de conexão geralmente segue este formato:
protocolo://usuario:senha@host/banco-de-dados?opcoes
A abordagem moderna
Este é o coração do processo. Vamos focar em estabelecer e gerenciar a conexão de forma profissional, com código limpo e modular.
Criando um módulo de conexão dedicado - (clique para expandir):
A lógica de conexão não deve ficar no arquivo principal da aplicação. A melhor prática é isolá-la em um módulo próprio, como
config/database.js
. Essa separação de responsabilidades melhora a organização, a manutenibilidade e a testabilidade do código, pois facilita a criação de mocks do banco de dados em testes.A conexão com async/await e tratamento de erros - (clique para expandir):
Dentro do módulo de conexão, usamos
async/await
com um bloco try/catch
para um tratamento de erros claro e eficaz.// config/database.js import mongoose from 'mongoose'; import 'dotenv/config'; const connectDB = async () => { try { await mongoose.connect(process.env.MONGO_URI); console.log('Conexão com o MongoDB estabelecida com sucesso!'); } catch (error) { console.error('Erro ao conectar com o MongoDB:', error.message); // Encerra a aplicação em caso de falha na conexão process.exit(1); } }; export default connectDB;
A chamada a
process.exit(1)
no catch
é uma prática defensiva que impede a aplicação de continuar rodando em um estado inválido se a conexão com o banco falhar.Gerenciando o ciclo de vida da conexão - (clique para expandir):
A conexão com o banco de dados tem um ciclo de vida. Monitorar eventos como desconexões e reconexões é muito importante em produção. O Mongoose emite esses eventos por meio do objeto
mongoose.connection
.// Adicionar ao final do arquivo config/database.js mongoose.connection.on('connected', () => { console.log('Mongoose conectado ao DB.'); }); mongoose.connection.on('error', (err) => { console.error('Erro na conexão do Mongoose: ' + err); }); mongoose.connection.on('disconnected', () => { console.log('Mongoose desconectado.'); });
Da teoria à prática
Com a conexão pronta, vamos criar o modelo de dados que usaremos nos exemplos de CRUD.
Criando o Schema com validações - (clique para expandir):
Crie uma pasta
models
e, dentro dela, um arquivo User.js
. Este Schema de usuário será mais completo, incorporando validações do Mongoose.// models/User.js import mongoose from 'mongoose'; const { Schema } = mongoose; const userSchema = new Schema({ name: { type: String, required: true, trim: true // Remove espaços em branco no início e no fim }, email: { type: String, required: true, unique: true, // Garante que o email seja único na collection lowercase: true, // Converte o email para minúsculas antes de salvar trim: true }, password: { type: String, required: true, minlength: 6 // Define um comprimento mínimo para a senha }, createdAt: { type: Date, default: () => Date.now(), // Define um valor padrão immutable: true // Torna o campo imutável após a criação }, updatedAt: { type: Date, default: () => Date.now() } }); const User = mongoose.model('User', userSchema); export default User;
Cada opção (
required
, unique
, lowercase
, default
, immutable
, minlength
) adiciona uma camada de consistência e segurança aos seus dados.Compilando o Schema em um Model - (clique para expandir):
A linha
const User = mongoose.model('User', userSchema);
compila a "planta baixa" (Schema) em um Model, que é a classe que sua aplicação usará para interagir com os dados dos usuários.Realizando operações CRUD com Mongoose
Esta seção tem exemplos de código para cada operação CRUD. Todos usam
async/await
e nomes fictícios.Create - (clique para expandir):
Existem duas formas principais de criar documentos. A primeira, mais explícita, é instanciar um objeto do modelo e chamar
.save()
.const newUser = new User({ name: 'Laís', email: 'lais@rocketseat.team', password: 'senha_super_segura' }); await newUser.save();
A segunda forma, com
.create()
, é um atalho que instancia e salva em um único passo.const user = await User.create({ name: 'Diego', email: 'diego@rocketseat.team', password: 'outra_senha_segura' });
Read - (clique para expandir):
Para ler dados, os métodos mais comuns são:
User.find({})
: busca todos os documentos que correspondem a um filtro (um objeto vazio busca todos).
User.findOne({ email: 'diego@rocketseat.team' })
: busca o primeiro documento que corresponde ao filtro.
User.findById('id_do_documento')
: busca um documento pelo seu_id
.
Para otimizar suas consultas, use projeção para selecionar apenas os campos que você realmente precisa.
Update - (clique para expandir):
O método
findOneAndUpdate
é a forma moderna e atômica de encontrar e atualizar um documento. A opção { new: true }
é importante, pois faz com que o método retorne o documento já com as alterações aplicadas.const updatedUser = await User.findOneAndUpdate( { email: 'lais@rocketseat.team' }, { name: 'Laís Silva' }, { new: true } );
Delete - (clique para expandir):
Para remover documentos, você pode usar
deleteOne
ou findOneAndDelete
.await User.deleteOne({ email: 'diego@rocketseat.team' });
Em todas as operações de busca e modificação, considere usar o método
.exec()
. Embora await User.find()
funcione, as queries do Mongoose não são promises completas. Chamar await User.find().exec()
retorna uma promise real e fornece stack traces mais detalhados em caso de erro, uma prática que pode economizar tempo de depuração.Tópicos avançados
Para levar sua aplicação a outro nível, vamos abordar tópicos cruciais para performance e manutenibilidade.
Otimizando sua conexão - (clique para expandir):
O método
mongoose.connect()
aceita um objeto de opções para "afinar" o comportamento da conexão.Opção: | O que faz: | Quando usar: |
maxPoolSize | Define o número máximo de conexões abertas no pool. | Aumente se sua aplicação tem muitas operações concorrentes e lentas. |
minPoolSize | Define o número mínimo de conexões a serem mantidas abertas. | Aumente para evitar a latência de abrir novas conexões em apps com picos de uso. |
serverSelectionTimeoutMS | Tempo (ms) que o driver tenta encontrar um servidor antes de dar erro. | Diminua (ex: 5000) para falhar mais rápido em desenvolvimento. Mantenha o padrão (30000) em produção com replica sets. |
socketTimeoutMS | Tempo (ms) que um socket pode ficar inativo antes de ser fechado. | Ajuste se você tem operações muito longas que excedem o timeout padrão. |
Middleware do Mongoose - (clique para expandir):
Os hooks de middleware
pre
e post
são ferramentas poderosas para executar lógica em pontos específicos do ciclo de vida de um documento, como antes de salvar. Um ótimo exemplo é usar um pre('save')
para atualizar automaticamente o campo updatedAt
.// Adicionar ao User.js userSchema.pre('save', function(next) { this.updatedAt = new Date(); next(); });
E para você, que já está construindo aplicações robustas com Node.js, há um próximo passo inevitável na sua evolução: a inteligência artificial. A IA não é mais o futuro, é a ferramenta que está redefinindo o presente do desenvolvimento. Para entender como essa revolução acontece na prática e como ela pode acelerar sua carreira, assista ao vídeo especial que preparamos:

Conclusão
Você aprendeu não apenas a conectar uma aplicação Node.js ao MongoDB, mas a fazê-lo de forma segura, organizada e pronta para escalar. O domínio da interação com o banco de dados é um passo de grande importância na sua evolução contínua como profissional de tecnologia.
Para solidificar o conhecimento, experimente implementar um CRUD completo para uma nova entidade ou explore a documentação oficial do Mongoose para descobrir mais validadores e opções de query.
E se você quer aprender tudo sobre Node.js, dominando uma linguagem flexível e popular para construir arquiteturas modernas, eficientes e escaláveis, confira nossa formação completa:
E chegamos ao fim! Esperamos ter ajudado e torcemos que o conteúdo tenha sido útil para você. Se pintar qualquer outra dúvida, já sabe: é só chamar a gente na comunidade.
Artigos_
Explore conteúdos relacionados
Descubra mais artigos que complementam seu aprendizado e expandem seu conhecimento.