Neste tutorial você vai aprender como criar uma aplicação web básica, apenas com listagem e cadastro, utilizando a stack Node.js, Express, EJS, Mongoose e MongoDB. É um excelente tutorial para quem não sabe nada de Node.js e/ou de algum dos componentes desta stack de tecnologia que citei, mas está longe de ser um material definitivo sobre o assunto.
Um outro excelente tutorial que faz a mesma coisa que este, mas que não usa Mongoose é este aqui. E se você não quer saber de MongoDB, mas sim de SQL, mude para este tutorial (inclui vídeo).
Ao término do artigo você encontrará um formulário para baixar os fontes, bem como outros posts adicionais, meus livros, cursos, etc. Qualquer dúvida que tiver sobre o assunto, deixe nos comentários.
Neste artigo você vai ver:
- Configurando o ambiente
- Entendendo o Express
- Preparando o banco de dados
- Conectando no Mongo com Node
- Escrevendo no banco
Vamos lá!
#1 – Configurando o ambiente
Nesta parte vamos instalar e configurar o ambiente necessário para o restante do tutorial. Vamos começar instalando o Node.js. Bem fácil: apenas clique no link do site oficial e depois no grande botão verde para baixar o executável certo para o seu sistema operacional, sendo recomendada a versão LTS. Esse executável irá instalar o Node.js e o NPM, que é o gerenciador de pacotes do Node. No vídeo abaixo eu mostro o passo a passo.
Uma vez com o NPM instalado, Crie uma pasta para guardar os seus projetos Node no computador e, dentro dela, via terminal de comando com permissão de administrador, rode o comando abaixo para instalar o meu fork do projeto Express Generator, um criador de aplicações Express que vai facilitar bastante o nosso trabalho:
1 2 3 |
npm install -g https://github.com/luiztools/express-generator.git |
Depois de instalado, para executá-lo rode o seguinte comando, onde web-app é o nome do seu projeto:
1 2 3 |
express --git web-app |
O “–git” deixa seu projeto preparado para versionamento com Git. Aperte Enter e o projeto será criado.
Depois entre na pasta e mande instalar as dependências:
1 2 3 4 |
cd web-app npm install |
Ainda no terminal de linha de comando e, dentro da pasta do projeto, digite o comando abaixo:
1 2 3 |
npm start |
Isso vai fazer com que a aplicação inicie sua execução em localhost:3000, que você pode acessar pelo seu navegador.
OK, agora que temos a estrutura básica vamos fazer mais alguns ajustes em um arquivo que fica na raiz do seu projeto chamado package.json. Ele é o arquivo de configuração do seu projeto e determina, por exemplo, quais as bibliotecas que você possui dependência no seu projeto.
Precisamos alterar umas pequenas coisas nele, especificamente adicionar dependências para que o MongoDB funcione com essa aplicação usando o ORM Mongoose. Um ORM é uma biblioteca de programação que facilita o uso de bancos de dados e o Mongoose, em nosso caso, é um ORM para MongoDB.
Para adicionar dependências, usamos o NPM via linha de comando de novo:
1 2 3 |
npm i mongodb mongoose |
Com isso, duas dependências serão baixadas e duas novas linhas de dependências serão adicionadas para dar suporte a MongoDB. Ainda em seu diretório web-app, digite o comando abaixo para criar uma pasta:
1 2 3 |
mkdir data |
Nesta pasta vamos armazenar nossos dados do MongoDB, por uma questão de organização.
#2 – Entendendo o Express
Vamos abrir agora o arquivo app.js, que fica dentro do diretório da sua aplicação NodeJS (web-app no meu caso). Este arquivo é o coração da sua aplicação, embora não exista nada muito surpreendente dentro. Você deve ver algo parecido com isso logo no início:
1 2 3 4 5 6 7 8 9 10 |
var createError = require('http-errors'); var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); |
Isto define um monte de variáveis JavaScript e referencia elas a alguns pacotes, dependências, funcionalidades do Node e rotas. Rotas são módulos JavaScript que recebem as requisições do usuário à sua aplicação web – elas direcionam o tráfego e contém também alguma lógica de programação (embora você consiga, se quiser, fazer uma arquitetura MVC, se desejar). Quando criamos o projeto com o Express-Generator, ele criou estes códigos JS pra gente e vamos ignorar a rota ‘users’ por enquanto e nos focar no index, controlado pelo arquivo /routes/index.js.
Na sequência você deve ver:
1 2 3 |
var app = express(); |
Este é bem importante. Ele instancia o Express em uma variável app. A próxima seção usa esta variável para configurar os middlewares do Express. Um middleware é um módulo que faz algum processamento em cima de uma requisição recebida. Caso o processamento seja concluído com sucesso, ele repassa a requisição para o próximo middleware, caso contrário, dispara um erro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); app.use(logger('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); |
A configuração dos middlewares acima dizem ao app onde ele encontra suas views, qual engine usar para renderizar as views (EJS), define a interpretação do corpo de requisição (body parsing), como os cookies serão tratados e mais.
Note também que a última linha do segundo bloco diz ao Express para acessar os objetos estáticos a partir de uma pasta /public/, mas no navegador elas aparecerão como se estivessem na raiz do projeto. Por exemplo, mesmo se a pasta images fica em C:/node/web-app/public/images ela será acessada em http://localhost:3000/images
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// catch 404 and forward to error handler app.use(function(req, res, next) { next(createError(404)); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); |
Estes são manipuladores de erros para desenvolvimento e produção (além dos 404). Não vamos nos preocupar com eles agora, mas resumidamente você tem mais detalhes dos erros quando está operando em desenvolvimento.
1 2 3 |
module.exports = app; |
Uma parte importantíssima do Node é que basicamente todos os módulos exportam um objeto que pode ser facilmente chamado em qualquer lugar no código. Nosso app.js exporta seu objeto app.
OK! Vamos em frente, agora mexendo com persistência de dados.
#3 – Preparando o banco de dados
Agora vamos deixar nosso editor de texto um pouco de lado e voltar ao prompt de comando. Na verdade vamos primeiro usar nosso navegador para acessar o site oficial do MongoDB e baixar o Mongo, na versão Community Server. Esta é a versão gratuita e mais utilizada do MongoDB. No vídeo abaixo eu mostro todo o passo a passo de instalação.
Clique no link de download e busque a versão de produção mais recente para o seu sistema operacional. Baixe o arquivo e, no caso do Windows, rode o executável que extrairá os arquivos na sua pasta de Arquivos de Programas, seguido de uma pasta server/versão, o que é está ok para a maioria dos casos. Note que não há instalação do MongoDB, mas sim apenas uma extração de arquivos.
Pelo prompt de comando, entre na subpasta bin que fica dentro da pasta de instalação do seu MongoDB e digite o comando abaixo, substituindo o caminho passado ao executável pelo caminho até a pasta data do seu projeto (que você pode criar agora, caso ainda não tenha feito). Repare que em ambientes Unix, o executável deve ser chamado como ./mongod, sendo que no Windows é apenas mongod, como abaixo.
1 2 3 |
mongod --dbpath c:/node/web-app/data |
Isso irá iniciar o servidor do Mongo, o que pode demorar um pouco na primeira vez. Uma vez que o prompt pare de correr texto e não tenha parado em um erro, está pronto, o servidor está executando corretamente. Na pasta data do seu projeto você vai ver que foram criados uma série de arquivos. Caso esteja usando Git, para que estes arquivos não vão parar no repositório (o que seria uma má prática), inclua a palavra ‘data’ (sem aspas) no seu arquivo .gitignore que deve estar na raiz do projeto.
Agora abra outro prompt de comando (o outro ficará executando o servidor) e novamente dentro da pasta bin do Mongo, digite o comando abaixo para abrir o Mongo Shell, um client de linha de comando para MongoDB:
1 2 3 |
mongo |
Se você olhar no prompt onde o servidor do Mongo está rodando, verá que uma conexão foi estabelecida. mongod é o executável do servidor, e mongo é o executável de cliente, que você acabou de conectar. Note que não precisamos informar nenhuma configuração, já que nosso server de desenvolvimento não possui senha e está rodando na porta padrão (27017).
Opcionalmente você pode usar ferramentas visuais como MongoDB Compass, que particularmente eu gosto de utilizar e que ensino no vídeo abaixo.
No console do cliente Mongo Shell que acabou de conectar ao banco de dados, digite:
1 2 3 |
use web-app |
Agora estamos usando a base “web-app.” No entanto, ela somente será criada de verdade quando adicionarmos registros nela, o que faremos a partir do próprio cliente para exemplificar.
Uma de minhas coisas favoritas sobre MongoDB é que ele usa JSON como estrutura de dados, o que significa curva de aprendizagem zero para quem já conhece o padrão que é a base dos objetos JavaScript.
Vamos adicionar um registro à nossa coleção (o equivalente do Mongo às tabelas do SQL). Para este tutorial teremos apenas uma coleção de usuários e emails em nossa base, sendo o nosso formato de dados como abaixo:
1 2 3 4 5 6 |
Para cadastrar um usuário com o formato acima, basta usarmos o comando insert.
1 2 3 |
Uma coisa importante aqui: “db” é a base de dados na qual estamos conectados no momento, que um pouco antes havíamos definido como sendo “web-app”. A parte “users” é o nome da nossa coleção, que passará a existir assim que adicionarmos um documento JSON nela. Tecle Enter para que o comando seja enviado ao servidor. Se tudo deu certo, você receberá um WriteResult (resultado de escrita) com o número de documentos inseridos na base (nInserted).
Para ver se o registro foi parar no banco corretamente, vamos fazer um find na coleção users:
1 2 3 |
db.users.find().pretty() |
O pretty() no final do comando find() é para identar o resultado, que retornará como abaixo. Repare que foi gerado um campo _id que é a chave primária para os documentos do MongoDB e que é criado automaticamente.
1 2 3 4 5 6 7 |
{ "_id" : ObjectId("607c8f6f3e82c2f4c2875bcc"), "username" : "testuser1", } |
Claro, o seu _id vai ser diferente desse, uma vez que o Mongo irá gerá-lo automaticamente. Isto é tudo que precisamos saber de MongoDB no momento, o que me parece bem fácil, aliás! Para saber mais sobre o MongoDB, consulte este guia de estudos!
Agora vamos adicionar mais alguns registros no seu console Mongo Shell:
1 2 3 4 |
const moreUsers = [{ "username" : "testuser2", "email" : "[email protected]" }, { "username" : "testuser3", "email" : "[email protected]" }] db.users.insert(moreUsers); |
Nesse exemplo passei um array com vários objetos para nossa coleção. Usando novamente o comando db.users.find().pretty() irá mostrar que todos foram salvos no banco.
Agora sim, vamos interagir de verdade com a web app + MongoDB.
Caso tenha gostado de mexer com MongoDB e queira se aprofundar mais no assunto, recomendo o curso que criei, que você pode conhecer clicando no banner abaixo:
#4 – Conectando no Mongo com Node
Vamos começar construindo uma página que mostre os registros de usuários que temos no nosso banco de dados (lembra-se que no trecho anterior eu adicionei alguns registros manualmente?). Meu objetivo agora é que você consiga criar um HTML com a seguinte aparência, considerando que esses dados vieram do banco:
Nada muito avançado, apenas uma lista de usuários com um link para lhes enviar um email. Na verdade nesta etapa do tutorial meu objetivo é lhe ensinar como ler e escrever no MongoDB a partir do Node.js, não vamos criar um site completo e/ou complexo.
Primeiramente, vamos criar um novo arquivo chamado db.js na raiz da nossa aplicação Express (web-app). Esse arquivo será o responsável pela conexão e estrutura do nosso banco de dados, usando o ORM Mongoose. Adicione estas duas linhas:
1 2 3 4 |
const mongoose = require('mongoose'); mongoose.connect('mongodb://127.0.0.1:27017/web-app'); |
Estas linhas carregam o objeto Mongoose (que já havíamos deixado instalado na etapa de dependências no início do tutorial) e com ele fazemos a conexão em nosso banco de dados localhost (127.0.0.1), sendo 27017 a porta padrão do MongoDB.
Na sequência, ainda no db.js, adicione as seguintes linhas:
1 2 3 4 5 6 7 8 9 10 11 12 |
const mongoose = require('mongoose'); mongoose.connect('mongodb://127.0.0.1:27017/web-app'); const userSchema = new mongoose.Schema({ username: String, email: String }, { collection: 'users' } ); module.exports = { Mongoose: mongoose, UserSchema: userSchema } |
Aqui definimos a estrutura da nossa coleção users e exportamos um objeto contendo o Mongoose e a estrutura (schema) da nossa coleção de usuários, para que possamos usar este objeto em outras partes da aplicação.
A seguir, vamos modificar a nossa rota para que ela mostre dados vindos do banco de dados, usando esse db.js que acabamos de criar. Abra o arquivo /routes/index.js no seu editor de texto e modifique a rota que está lá e que hoje renderiza uma index vazia para que fique como abaixo:
1 2 3 4 5 6 7 8 9 10 |
/* GET home page. */ router.get('/', async (req, res) => { const db = require("../db"); const Users = db.Mongoose.model('users', db.UserSchema, 'users'); const docs = await Users.find({}).lean().exec(); res.render('index', { docs }); }); |
OK … aqui compliquei um pouco as coisas.
router.get define que estamos esperando requisições HTTP GET no path (caminho) /.
O comando require está voltando uma pasta e carregando o conteúdo do arquivo db.js (extensão desnecessária) que, por sua vez, está carregando a conexão com o banco de dados (via Mongoose) e o esquema da coleção de users.
db.Mongoose.model carrega a coleção users e o esquema de usuários dela. Usamos essa coleção para dar um find por todos usuários (filtro vazio = {}). O lean() é opcional aqui, mas uma boa prática de performance, para retornar um JSON text-plain ao invés de objetos Mongoose complexos.
O comando exec executa a consulta em si, retornando um array docs, que contém os resultados da pesquisa. Por fim, apenas mandei esses docs serem renderizados na view users com res.render.
Repare que usei duas palavras reservadas do JavaScript que talvez sejam novidade para você: async e await. O async antes da função indica que a mesma é assíncrona, algo necessário para lidar com banco de dados. Já o await diz que, de maneira simplista, somente a execução deve seguir para a linha de baixo depois que o retorno do banco for concluído. Falo mais disso neste vídeo.
Agora vamos editar a nossa /views/index para listar os usuários. Entre na pasta views e crie um arquivo users.ejs. Então edite o HTML para que fique desse jeito:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE html> <html lang="en"> <head></head> <body> <h1 class="text-center title-1"> Lista de Usuários </h1> <ul> <% docs.forEach((user)=> { %> <li><a href="mailto:<%= user.email %>"> <%= user.username %> </a></li> <%})%> </ul> </body> </html> |
Aqui estamos dizendo que o objeto docs, que será retornado pela rota que editamos no passo anterior, será iterado com um forEach e seus objetos utilizados um-a-um para compor uma lista HTML não-ordenada com seus emails e senhas.
Isto é o bastante para a listagem funcionar. Salve o arquivo e reinicie o servidor Node.js. Ainda se lembrar de como fazer isso? Abra o prompt de comando, derrube o processo atual (se houver) com Ctrl+C e depois:
1 2 3 |
npm start |
Agora abra seu navegador, acesse http://localhost:3000/e maravilhe-se com o resultado.
Se você viu a página acima é porque sua conexão com o banco de dados está funcionando!
Agora vamos finalizar nosso projeto!
#5 – Escrevendo no banco
Salvar dados em um banco de dados não é algo particularmente difícil. Essencialmente precisamos definir uma rota para receber um POST, ao invés de um GET.
Primeiro vamos criar a nossa tela de cadastro de usuário com dois clássicos e horríveis campos de texto à moda da década de 90. Dentro da pasta views, crie um user.ejs com o seguinte HTML dentro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!DOCTYPE html> <html lang="en"> <head></head> <body> <h1 class="text-center title-1"> Cadastro de Usuário </h1> <form action="/user" method="post"> <p>Username:<input type="text" name="username" /></p> <p>Email:<input type="text" name="email" /></p> <p><input type="submit" value="Salvar" /></p> </form> </body> </html> |
Agora vamos voltar à pasta routes e abrir o nosso arquivo de rotas, o index.js onde vamos adicionar duas novas rotas. A primeira, é a rota GET para acessar a página user.ejs quando acessarmos /user no navegador:
1 2 3 4 5 6 |
/* GET New User page. */ router.get('/user', (req, res) => { res.render('user', { title: 'Cadastro de Usuário' }); }); |
Se você reiniciar seu servidor Node e acessar http://localhost:3000/user verá a página abaixo:
Enquanto que ao digitar um endereço na barra do navegador ou clicar em um link você faz uma requisição HTTP GET, quando você clica em um botão de salvar em um formulário você estará fazendo uma requisição POST. Se você preencher esse formulário agora e clicar em salvar, dará um erro 404. Isso porque ainda não criamos a rota que receberá o POST desse formulário!
Aqui o processo não é muito diferente do que fizemos para listar os usuários, ou seja, criar uma rota para que, quando acessada (POST nesse caso) nós chamaremos o objeto de banco para salvar os dados nele. A rota, nesse caso, é /user novamente, mas a função será diferente.
Então abra novamente o arquivo /routes/index.js e adicione o seguinte bloco de código logo após as outras rotas e antes do modules.export:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* POST new user */ router.post('/user', async (req, res) => { const username = req.body.username; const email = req.body.email; const db = require("../db"); const Users = db.Mongoose.model('users', db.UserSchema, 'users'); const user = new Users({ username, email }); try { await user.save(); res.redirect("/"); } catch (err) { next(err); } }); |
Obviamente no mundo real você irá querer colocar validações, tratamento de erros e tudo mais, eu apenas coloquei um try/catch para em caso de erro, o próximo middleware do Express assumir o tratamento do mesmo (tem um middleware configurado pra erro no app.js). Aqui, apenas carregamos o objeto db onde temos a conexão com o Mongoose, pegamos o username e email vindos no corpo da requisição (foram postados pelo formulário, lembra?) e depois carregamos o model da coleção de usuários.
Então entram as novidades: uma vez com o model carregado, podemos criar novos objetos do mesmo tipo, populando seus campos e depois mandando salvar os mesmos. A save() não retorna nada de especial e se tudo correu bem, retornamos o fluxo à tela de listagem de usuários, onde devemos ver um novo usuário cadastrado.
Com isso estamos oficialmente lendo e escrevendo dados em um banco MongoDB a partir de uma aplicação Node.js com Express, EJS e Mongoose. Se conseguiu executar este tutorial até aqui, você é o que chamam atualmente de desenvolvedor full stack. Ok, não é um BOM dev full stack, mas isso o tempo resolve. 🙂
Se for colocar um projeto em produção, você pode fazê-lo usando a Umbler, a Heroku, a AWS ou a Digital Ocean. Tirando a primeira que dispensa tutoriais, as demais tem tutoriais aqui no blog.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.
Luiz, consegui fazer todo o tutorial e deu certo por aqui, curti bastante trabalhar com Node e agora vou pro tutorial de “Como rodar NodeJS em servidor Windows”. Valeu pelo conteúdo. Abraço
Disponha sempre que precisar. Também tem diversos outros tutoriais de Node.js aqui no blog, é só dar uma olhada nesta categoria: http://184.73.67.74/categoria/nodejs
Obrigado. Deu certo agora
Boa noite, Luiz … Muito obrigado pela farta quantidade de material. Estrou preparando um minicurso sobre MEAN. E, o seu material tem me ajudado muito. Felicidade!
Obrigado pelo feedback Manoel. O M, o E e o N você encontra aqui no blog, vou ficar te devendo só o A, hehehe. Fico feliz em estar ajudando!
Isso fica mais fácil aprender com Vue né? tipo MEVN?
Obrigado o tutorial top!
Não sei dizer se é mais fácil, sempre fui mais focado em backend. De front eu curto mais JQuery + Bootstrap, hehehehe
No entanto React está mais no meu radar, pra fazer um MERN
Entendi hehehe, agora estou mais focado em Vue e só falta aprender a criar API com banco de dados MongoDB que você acabou de criar o tutorial e vou ler. Valeu!
Luiz, em primeiro lugar, o tutorial me ajudou muito, valeu.
Em segundo… como eu faria para relacionar outra coleção com essa? Sei que o node não é relacional, mas vi que dá pra usar algo como ‘Schema.Types.ObjectId’ para relacionar, mas não consigo entender como esse vinculo funcionaria em uma api como essa, e como usar find e save neles.
Dá pra dar uma luz, rs?
O Mongo não é relacional e não há integridade referencial com chave estrangeira para garantir a consistência dos dados entre coleções diferentes. Sendo assim, você até pode usar o ObjectId de uma coleção em um documento da outra, mas terá de controlar na mão essas referências.
Que mal lhe pergunte, porque deseja fazer isso? Muitas vezes existem outras abordagens melhores com MongoDB.
Opa, valeu a rápida resposta. Na verdade o que preciso, pelo que andei lendo depois que enviei este comentário, é criar um subdocumento, creio… Até consegui criar o subdocumento, mas não consigo usar os métodos get e post para inserir e listar valores do banco de dados.
Exato. A maioria dos relacionamentos são substituídos por subdocumentos. Sobre o seu problema, via linha de comando você consegue inserir e listar valores? Dá uma olhada na minha série de posts sobre MongoDB pra aprender a usar o banco pra valer mesmo. Esse tutorial aqui tem como foco maior o Node.js.
Blz Luiz, vou dar uma olhada com calma e retorno depois. Valeu man!
Bom pra mim nao rolou 🙁 nao estao carregando a view newuser dando erro 404.
Boa noite Euler. Provavelmente você não definiu o arquivo de rota corretamente, ou então não carregou o arquivo corretamente no app.js. Sugiro baixar o zip desse projeto e dar uma conferida geral.
Mais um excelente post seu, como sempre, Luiz. Muitos complicam . Voce sempre “descomplica”, com uma didática excelente. Obrigado.
Fico feliz que goste dos materiais aqui do blog!
Ótimo o tutorial. Tem um repositório no GtiHub com o código dele? o/
Obrigado pelo feedback. Basta colocar o email no formulário ao final do post que você recebe os fontes.
É, pouco depois de fazer o comentário eu acabei vendo. Rsrsrs. Obrigado!
Novamente, ótimo tutorial. Segue uma sugestão: você poderia atualizar este post acrescentando o nodemon, assim não é necessário ficar iniciando o node toda vez com o npm start.
Só complementando, a parte para montar o servidor automaticamente com toda a estrutura foi show (app.js), mas se eu usar apenas o comando de “npm install express” ao invés de usar o “npm install express-generator” o comando para gerar a estrutura funcionará “C:node> express -e –git nodetest1”?
Não, na própria página do Express no NPM eles falam do express-generator para gerar o scaffold do projeto: https://www.npmjs.com/package/express
Vou dar uma olhada, já ouvi falar e nunca usei. Obrigado pelo feedback.