Como fazer upload de arquivos no AWS S3 em Node.js

Anteriormente eu escrevi um tutorial aqui no blog de como fazer upload de arquivos em Node.js, usando as bibliotecas Formidable e Multer. Se você não sabe como fazer isso ainda, esse é um pré-requisito para este tutorial, então volte lá e leia o outro tutorial!

No entanto, em aplicações web profissionais, quer elas sigam a metodologia de 12-Factor App ou não, você NÃO DEVE armazenar os arquivos carregados (que fizeram upload) na sua aplicação junto ao servidor da mesma. Ao invés disso, você deve armazenar em um serviço próprio para armazenamento de arquivos.

Alguns motivos incluem:

  • o servidor da sua aplicação deve ser descartável, principalmente se estiver usando Docker, mas os arquivos carregados não são descartáveis;
  • o custo de armazenamento do servidor da sua aplicação muitas vezes é maior que o custo de armazenamento de um serviço de armazenamento;
  • quanto maior o tamanho do servidor da sua aplicação, mais difícil é para fazer backups e restaurações, e arquivos carregados impactam muito no tamanho;
  • centralizar os arquivos carregados em um serviço de armazenamento lhe permite uma gestão melhor e mais apurada;

Entendido este ponto, se você for ter muitos arquivos carregados na sua aplicação, é hora de procurar uma solução melhor para armazená-los. Dependendo do tipo de conteúdo, periodicidade de acesso, necessidades específicas de busca, etc existem diversas soluções que podem ser usadas.

Arquivos simples de baixa criticidade, em aplicações pequenas? Que tal o Google Drive (tutorial aqui)!

Arquivos em grande quantidade mas com baixa frequência de acesso (como backups e “arquivo morto”)? Que tal o AWS Glacier?

Documentos e outros arquivos de cadastro com informações que precisam ser auditadas, consultadas, preocupação com LGPD, etc? Procure por ECMs (Enterprise Content Management) como o Alfresco.

Para os demais cenários, eu recomendo o AWS S3 (sigla para Simples Storage Service).

O S3 é um disco virtual, que a Amazon chama de bucket (balde), onde você cria e faz a gestão de diretórios e arquivos de qualquer tipo. Ele pode ser de acesso público ou privado, pode estar “atrás” de CDNs (como o AWS CloudFront) para baixar a latência de acesso, pode ser integrado com soluções Serverless, pode disparar eventos na fila da Amazon (o SQS) e muito mais.

Ou seja, ele é muito mais do que uma pasta ‘uploads’ no servidor da sua aplicação seria, e tudo isso por um preço bem baixo, baseado em centavos de dólar por GB de armazenamento e mais alguns centavos de dólar por GB de download cada vez que um arquivo é requisitado.

Você vai ver neste tutorial:

  1. Setup do Bucket
  2. Setup do Projeto
  3. Upload no S3

Vamos lá!

#1 – Setup do Bucket

O primeiro passo obviamente é você ter uma conta na AWS, se autenticar no seu painel e ir na área de credenciais de segurança, que ficam dentro da sua conta de usuário (o menu com o seu nome no topo).

Uma vez dentro da área de credenciais de segurança, crie chaves de acesso (caso ainda não possua), lembrando que você pode ter até duas chaves apenas e que a Amazon não fornece backup, então guarde-as em um lugar seguro.

As chaves são compostas de um Access Key e de um Access Secret, sendo este último o mais “sensível” e que não deve ser compartilhado com ninguém. Usaremos estas informações mais tarde.

Agora vá no menu de Serviços na sua conta da AWS e procure por S3, para acessar a tela de listagem de buckets, como abaixo. Clique na opção Criar bucket em laranja.

Abrirá uma tela de configuração do bucket, onde inicialmente você tem de dar um nome ao mesmo, a região onde ele será criado (crie próximo do seu servidor de aplicação) e permissões de acesso, que geralmente o recomendado é que seja privado, a menos que esteja criando um servidor de arquivos público.

Se estiver lidando com arquivos de conteúdo sensível, você pode habilitar a opção de criptografia dos dados do disco e até do versionamento do mesmo, caso precise voltar em algum ponto do tempo. Note que essas opções consumirão mais espaço e consequentemente sua conta ficará maior no final do mês.

Você será direcionado para a tela de listagem de buckets novamente e clicando no nome do seu bucket você irá para uma área onde pode mudar as configurações realizadas mas, principalmente, gerenciar os arquivos de seu bucket: pode criar e excluir pastas, fazer upload e download de arquivos, etc.

Importante salientar que a exclusão de pastas ou até mesmo do próprio bucket, só podem ser feitos quando os arquivos dentro da pasta ou do bucket tiverem sido todos excluídos. Este é um mecanismo de segurança contra exclusão acidental de pastas e do bucket em si.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

#2 – Setup do Projeto

Aqui vou considerar que você já tem uma aplicação web em Express funcionando e que ela já faz upload de arquivos para o servidor. Você deve ter um código parecido com este aqui, onde um arquivo é recebido via POST e salvo no servidor.

Caso não entenda nada do código acima, recomendo realizar primeiro o tutorial de upload de arquivos. Caso entenda mas não tenha o projeto acima, você pode fazer o download através do formulário ao final deste tutorial ou simplesmente adaptar os códigos a seguir.

Em seu projeto Node.js e instale nele duas dependências, o AWS SDK e o dotenv.

O AWS SDK é a biblioteca de componentes prontos para usarmos os recursos da AWS em Node.js. Já o dotenv é um popular pacote para fazer gestão de variáveis de ambiente e configurações da sua aplicação.

Como o AWS SDK exige uma série de configurações de ambiente para funcionar adequadamente, usaremos o dotenv para fazer isto.

Agora, crie um arquivo criar arquivo .env na raiz do seu projeto, com o seguinte conteúdo.

Lembra das credenciais de acesso que criamos no início do tutorial? Pois é, precisaremos delas aqui, coloque o access key e o access secret neste arquivo, nos referidos campos.

O AWS_REGION você também determinou quando criou o bucket, mas aqui deve ir somente a sigla dele. Você consegue bem fácil na sua conta, é só ver a região que está listada no topo da sua conta e usar a sigla dela na configuração do .env, será algo como us-east-1.

O nome do bucket em si também foi definido na criação e você não deve ter dificuldades em achar.

#3 – Upload no S3

Agora que temos a infraestrutura e o projeto criados e configurados, é hora de construirmos o código que enviará os arquivos do nosso servidor para o bucket no S3.

Crie um arquivo s3Client.js na raiz do seu projeto e inclua o código abaixo dentro dele, que comentarei a seguir.

A primeira linha é apenas um require para carregar o AWS SDK que instalamos antes e o fs, para podermos ler o arquivo do nosso servidor que queremos enviar para a Amazon.

Abaixo, temos duas funções, a primeira que é a que faz o upload para o S3, e que vou explicar melhor abaixo, e uma segunda que deixei de brinde, que é para pesquisar pastas e arquivos no seu S3.

No uploadFile, que é o nosso foco aqui, nas primeiras linhas eu instancio um objeto S3 com uma classe do pacote AWS-SDK e leio os bytes do arquivo que vou enviar para a nuvem da Amazon.

A seguir, configuro os parâmetros deste meu envio: o nome do bucket (que estou pegando do dotenv), o nome de destino do arquivo (key) e o conteúdo em bytes do mesmo (body). Opcionalmente eu posso passar o Content-Type/Mime-Type do arquivo, caso seja de algum tipo que a AWS não reconheça sozinha. Se ela não reconhecer, você sempre terá de fazer download do arquivo quando tentar acessar o mesmo, já se ela reconhecer como vídeo, por exemplo, poderá assistir o vídeo no navegador.

Note que chamei uma função .promise() ao fim do upload. Isso porque, por padrão, o upload espera um callback e se quisermos trabalhar com Promises, precisamos desta última chamada que adicionei. Também note que estou retornando apenas a propriedade Location do do data, que é a URL do seu arquivo no S3.

Após exportar nossa função no s3Client.js, vamos voltar ao código em que fazemos upload para o servidor da aplicação e vamos alterar o código para que, uma vez que o arquivo chegue no servidor, a gente envie ele para a AWS usando nosso s3Client.

Aqui, modifiquei o callback da função parse do Formidable para que envie o arquivo que acabamos de fazer upload para o S3, usando nosso client. Atenção ao fato de que files é o objeto do Formidable que traz os arquivos submetidos no formulário e filetoupload é o name do campo file do meu HTML, contendo as propriedades name (nome original do arquivo) e path (caminho do arquivo já no meu servidor).

Utilizei de Async/Await para que a legibilidade fique mais simples e pego a URL retornada e imprimo na tela da aplicação.

Antes de testar nossa aplicação, você deve carregar as variáveis de ambiente, o que pode ser feita no código mas eu cada vez mais tenho preferido colocar no próprio comando de inicialização mesmo, como abaixo (package.json).

Agora suba a sua aplicação com npm start e teste o upload que deve aparecer lá no seu painel do S3, na Amazon.

Se você tiver um erro do tipo “AccessDenied: Access Denied” quer dizer que as suas credenciais no dotenv estão erradas, inválidas ou que escreveu o nome das variáveis erroneamente. Nos dois primeiros casos, recomendo que volte ao passo inicial de setup aqui do tutorial e na área de credenciais, exclua as existentes e recrie.

Já se escreveu erroneamente o nome das variáveis de ambiente no arquivo .env, apenas refaça esta etapa aqui no tutorial. Elas são carregadas automaticamente e por causa disso precisam estar 100% iguais ao que manda a documentação da AWS, que é o que apresentei mais cedo.

Que tal um upload de arquivos para o S3 disparar uma função serverless/Lambda na AWS?

Além disso, caso você trabalhe forte o conceito de testes unitários, pode ser útil aprender a mockar as APIs da AWS em seus testes.

Deixa aí nos comentários!

Quer ver na prática como utilizar o S3 (e vários outros serviços da AWS) para, por exemplo, hospedar aplicações em ReactJS? Conheça meu curso clicando no banner abaixo!

Curso FullStack
Curso FullStack

Como fazer upload de arquivos em Node.js

Formulário de Upload

Atualizado em 09/04/2021!

Eventualmente alguém me pergunta como fazer upload de arquivos em Node.js. Como é um assunto que já está na minha pauta há algum tempo, resolvi fazer este tutorial pra ajudar o pessoal. Você vai ver que não é algo muito difícil e este tutorial vai ser bem curto.

Parto do princípio aqui que você já sabe o básico de Node.js, JavaScript e de HTML, conhecimentos estes que podem ser obtidos em outros artigos aqui do blog, em meu livro, em meu curso ou até mesmo no meu canal do Youtube.

Vamos começar instalando o express-generator, pacote que nos ajuda a criar aplicações web com ExpressJS, o mais famoso web framework para Node.js (você vai precisar de permissão de administrador no terminal de linha de comando):

Agora mande criar um novo projeto Express usando o comando abaixo, onde chamo o projeto de fileupload:

Após entrar na pasta e instalar as dependências com npm install, instale a dependência formidable, que utilizaremos para lidar com o upload de arquivos em Node.js, dentre tantas alternativas existentes na Internet.

Agora vá na pasta views e abra o arquivo index.ejs para editarmos o HTML do projeto, deixando-o como abaixo:

Note que não tem nada demais aqui, apenas um formulário com um campo de upload de arquivo, mas repare que o formulário possui o enctype como multipart/form-data, isso é muito importante para que upload funcione!

Se você executar esta aplicação com npm start verá algo como abaixo:

Formulário de Upload
Formulário de Upload

Agora vamos programar o comportamento desta página. Fazemos isso indo na pasta routes e abrindo o arquivo index.js, onde temos as rotas da tela inicial.

Adicione a nova rota abaixo dentro deste arquivo:

O que fazemos aqui?

Carregamos a extensão formidable que instalamos há pouco, recebemos o form POSTado pelo usuário e salvamos o arquivo através da função form.parse. Se você testar agora já deve estar funcionando.

File Uploaded
File Uploaded

Mas…onde que o arquivo foi salvo?

Por padrão ele fica salvo em uma pasta temporária no seu computador. Para mudar isso, vamos modificar nosso código anterior para que usando a biblioteca fs (file system) do Node possamos mover o arquivo que fizemos upload para a mesma pasta do seu projeto:

Note que carreguei o módulo fs lá no início do código e que depois uso este módulo para mover o arquivo de lugar no sistema operacional usando a função renameSync (que dispensa o uso de callbacks e promises). Para definir ONDE é este lugar de destino, usei o path.join para montar um caminho contendo o __dirname (pasta atual deste módulo), voltar uma pasta (..) e com isso chegar na raiz do projeto.

Já o arquivo que fiz upload é facilmente encontrado usando o argumento files, que é o terceiro da função form.parse.

O resultado na tela é esse:

File Moved
File Moved

Mas no seu computador o arquivo que você fez o upload estará disponível na pasta raiz do seu projeto.

Fácil, não é?!

Curso FullStack

Bônus: Mais uma opção

Agora caso tenha problemas com o formidable (eu nunca tive), você pode usar a dependência Multer:

E uma forma de utilizá-lo é essa:

Você pode usar o código acima reaproveitando a mesma view do exemplo anterior e o arquivo será salvo com nome aleatório na pasta uploads, na raiz do seu projeto.

Além disso, convém você não deixar os seus arquivos no mesmo servidor da aplicação. Para upload para serviços de armazenamento em nuvem, recomendo o AWS S3.

Espero ter ajudado!

Curtiu este tutorial? Conheça o meu curso online de Node.js e MongoDB para fazer aplicações web e APIs incríveis com esta fantástica plataforma. Basta clicar no banner abaixo!

Curso Node.js e MongoDB

Tutorial CRUD em Node.js com driver nativo do MongoDB – Parte 3

Lista de Clientes com Bootstrap

Atualizado em 01/04/2021!

O tutorial de hoje é uma continuação de uma série bastante acessada aqui no blog, onde ensino como fazer um sistema de cadastro bem simples em Node.js, usando o web framework ExpressJS e o banco de dados não-relacional MongoDB. Ou seja, o famoso CRUD.

Nesta terceira e última parte, introduzo um conceito importante de construção de interfaces que é a modularização da página através do recurso de partial views do EJS (view-engine que estamos usando com ExpressJS) e ensino como dar um “tapa” no visual usando o framework front-end Bootstrap, muito utilizado mundialmente para estruturar aplicações web responsivas e elegantes.

Para conseguir acompanhar todos os códigos, é importante que você tenha realizado a parte anterior ou pelo menos baixe os códigos-fonte que se encontram no formulário ao final do tutorial anterior (parte 2). Caso prefira assistir um vídeo, a primeira parte dessa séria pode ser resumida com este vídeo abaixo que é uma aula gratuita do meu curso de Node.js e MongoDB.

Aproveite!

O conteúdo do post é:

Vamos lá!

Atenção, uma versão ainda mais completa deste tutorial está disponível em videoaula em meu curso de Node.js e MongoDB.

Curso FullStack

O que são Partial Views?

É muito comum quando estamos construindo aplicações web que certas porções de nossas views (telas) acabem se repetindo. Por causa disso, todas as linguagens de programação para web possuem recursos de partial views, ou seja, de modularização da interface a partir de “partes” da tela que são construídas separadas e depois são montadas em conjunto para que o usuário veja apenas o resultado final, completo.

Fazendo uma analogia com o que fazemos no código backend do Node.js, uma partial view é como se fosse um módulo NPM, que depois usamos o require para carregá-lo em nosso código. Em outras plataformas isso é chamado de import, include, using, etc.

Nossa aplicação é bem simples, possui apenas três views: index, new e error, conforme você vê na imagem abaixo.

Estrutura do projeto
Estrutura do projeto
Essas views por sua vez também são mega simples, logo, não há uma reeeaaal necessidade de usar partial views aqui, usaremos mais a título de conhecimento. No entanto, esse conceito é muito poderoso e além de tornar seu código de front-end mais elegante e fácil de ler, ele torna a programação de novas páginas muito mais produtiva, pois sempre aproveitaremos elementos genéricos da interface que serão criados apenas uma vez.

Se olharmos agora para as views index e new, você deve notar que existem muitas tags em comum no início e no fim delas. É por aí que vamos começar a melhorar.

Estrutura que se repete
Estrutura que se repete

Partial views com EJS

Não é de hoje que eu uso EJS (Embedded JavaScript) em meus tutoriais e neste não será diferente. Apesar de não ser a view-engine (motor de visualização) padrão do ExpressJS (esse título é do PUG/Jade), ele é meu favorito pois a curva de aprendizado é ridiculamente baixa, considerando que usa HTML + JS, coisa que todo dev web está careca de saber.

Como não poderia deixar de ser, o EJS possui a funcionalidade de criar partial views (lembro dos meus tempos de ASP.NET em que chamávamos isso de MasterPages e WebUserControls…). Isso é bem fácil de fazer, comece criando um arquivo top.ejs (top=topo) na sua pasta views e nele vamos colocar toda a parte comum que teremos no topo de todas páginas do nosso sistema, que é apenas o HTML abaixo por enquanto:

Note que nesta top.ejs estamos usando algumas variáveis JS que devem vir do backend, através do model passado na função render do Node.js, lembra?

Agora vamos criar mais um arquivo novo, o bottom.ejs (bottom=rodapé), também na pasta views e nele vamos colocar toda a parte comum que teremos no rodapé de todas páginas do nosso sistema, que é apenas o HTML abaixo por enquanto:

E agora, como fazemos para as demais views do projeto usarem essas partial views para compor a sua estrutura?

Bem simples, vamos começar pela index.ejs, remova a parte do topo do arquivo que é repetida ao arquivo top.ejs e no lugar desse pedaço, deixe como abaixo, usando o comando ‘include’ para “incluir” a partial view na primeira linha do arquivo:

Note que já incluí também o include para o bottom.ejs no final do arquivo. Na hora que o arquivo HTML vai ser construído para ser enviado ao browser para renderização, o EJS vai ler essa linha e entender que parte desse HTML está em outro arquivo. Ele vai ir nesse outro arquivo, copiar o conteúdo de lá e colar bem nesse local.

Assim, com esses dois includes, nós mantemos nossa aplicação modularizada em três pedaços: topo, centro e rodapé, facilitando manutenção e aumentando o reuso de código.

Sim, reuso de código, pois agora nas páginas new.ejs e error.ejs nós podemos fazer os mesmos includes para não ter de repetir aqueles blocos iniciais e finais de HTML. Os ganhos agora são pequenos, mas no futuro, são enormes pois facilitam muito a evolução e manutenção do sistema web.

Eu não vou colocar aqui o código dos outros dois arquivos ejs com os includes, embora você possa baixar o projeto completo no final desse artigo deixando seu e-mail.

Depois de fazer estas alterações, coloque sua aplicação Node para rodar (não esqueça de subir o banco MongoDB também) e você vai ver que nada mudou para o usuário, que a tela continua renderizando do mesmo jeito e que as alterações só servem para ajudar o programador a trabalhar melhor mesmo.

Um dos grandes ganhos desta abordagem vem agora: manutenção. Vamos adicionar o framework front-end Bootstrap nessa aplicação pra dar um “tapa” no visual dela e vamos fazer isso de maneira muito fácil porque já deixamos a aplicação preparada para este tipo de alteração.

Adicionando o Bootstrap

Eu não vou me prolongar muito na teoria do Bootstrap por aqui pois falo bastante disso no meu livro Programação Web com Node.js. Inclusive aquele “B” na capa é o símbolo do Bootstrap (os outros símbolos são o JavaScript, o HTML5 e o MongoDB).

Basicamente o Bootstrap é um framework front-end muito popular no mundo inteiro, fornecendo uma estrutura e comportamentos padronizados que muitos devs web sabem como mexer, acelerando o desenvolvimento e manutenção de qualquer sistema que use Bootstrap como padrão para front-end.

É como se os sites tivessem a mesma estrutura básica, entende?

Apesar da estrutura básica ser a mesma, a aparência pode variar enormemente através da customização de arquivos de estilo (CSS).

A aplicação do Bootstrap em um projeto web se dá através de seus arquivos de CSS e de seus arquivos de JavaScript.

Vamos começar adicionando as duas dependências do Bootstrap (obtidas no site oficial), sendo que a primeira, de CSS, deve ser adicionada no topo do nosso arquivo top.ejs, substituindo a chamada original de CSS que estava lá:

Como adicionamos esta linha no top.ejs, TODAS as páginas que incluem essa partial-view vão estar agora com o CSS do Bootstrap. Isso é produtividade na programação!

Note que também adicionei duas meta-tags, conforme sugerido na página do Bootstrap, para garantir máxima compatibilidade com os recursos do framework, como a responsividade em dispositivos móveis.

Agora, vamos adicionar o script JS necessários pro Bootstrap funcionar corretamente no arquivo bottom.ejs. Ele deve ser adicionado logo antes da tag de fecha-body:

Só o fato de termos feito essas adições já vão gerar alguns efeitos visuais na nossa aplicação, como pode ver na imagem abaixo:

Mas para despertar todo o “poder embelezador” do Bootstrap em nossa aplicação nós temos de fazer alguns ajustes na nossa estrutura, bem como usar algumas classes CSS definidas pelo Bootstrap.

Assim, vamos começar definindo uma “div class container” que vai englobar todo o conteúdo do body das páginas. Para fazer isso, basta adicionar o conjunto de tags div abaixo, com a abre-div no top.ejs e a fecha-div no bottom.ejs, de modo que ambas fiquem dentro do body.

Note que só mudou uma linha, logo acima do H1, bem no final do top.ejs. Já no bottom.ejs, também só vai mudar uma linha, que fica bem no início do arquivo:

Após essa adição, teremos mais um ajuste perceptível nas páginas já existentes, que ficarão com uma margem e mais centralizadas. A div container faz a sua página funcionar em um grid-system de 12 colunas virtuais, ou seja, o Bootstrap permite que você organize o conteúdo das suas páginas em 12 blocos de tamanhos iguais, como em um Lego. Desde que você siga esta regra cuidadosamente, a sua aplicação web será responsiva, alterando a sua largura conforme a tela do aparelho que estiver acessando a mesma.

E isso é só o início!

Customizando a aparência

Eu poderia ficar aqui durante muitas e muitas horas escrevendo sobre Bootstrap, então vou ser breve. Vamos apenas dar uma embelezada na página para te mostrar o poder do framework e depois vamos encerrar o tutorial. Fica a seu critério depois se aprofundar, seja em meu livro ou na documentação oficial.

Uma vez adicionado a div container, que é o principal bloco da sua página, vamos personalizar os botões, ou os elementos que queremos que se pareçam com botões. Para isso, basta usarmos duas classes em cada elemento “btn btn-primary” para os botões principais e apenas “btn” para os secundários.

Cada âncora ou submit/button deve ficar parecida com o HTML abaixo:

O botão primário é azul no Bootstrap, mas isso obviamente você pode alterar sobrescrevendo ou editando o CSS original do Bootstrap, ou ainda através de templates que você pega na Internet. As classes CSS de botão também já adicionam um efeito de sombra ao passar o mouse por cima deles, o chamado “hover”.

Para “fechar” a tela inicial, vamos customizar a tabela além do botão, para ela ficar bem bacanuda. Para isso, além de usar o padrão HTML5 para tabelas (com thead, tbody, etc), vamos adicionar uma série de classes a saber:

  • table: classe padrão Bootstrap para tabelas;
  • table-bordered: com linhas nas bordas;
  • table-striped: com linhas “zebradas”;
  • table-hover: com sombra quando passa o mouse;
  • table-dark (no cabeçalho): pro cabeçalho ficar escuro;

O código final que lista os clientes cadastrados pode ser visto abaixo, e substitui a lista antiga:

Fechando de vez esta tela, atualize o código de paginação para ele ficar estilizado conforme recomendado pelo Bootstrap:

Com estas alterações, nossa tela inicial ficará muito melhor do que anteriormente, como mostra a imagem abaixo (coloquei btn-danger ao invés de btn-primary nos botões de exclusão):

Lista de Clientes com Bootstrap

Agora a segunda tela, de cadastro de cliente (new.ejs). Além de ajustarmos os botões com as classes que mencionei antes, vamos ajustar o form HTML conforme instruções do próprio Bootstrap:

O resultado, você confere na imagem abaixo (os tamanhos são ajustáveis, mas por padrão ele é responsivo):

Cadastro com Bootstrap

E com isso encerramos o tutorial de hoje e acredito que esta série sobre o CRUD com driver nativo do MongoDB. Qualquer dúvida que você tiver, deixe aí nos comentários que terei o maior prazer em responder.

Interessado em mais conteúdos sobre Node.js e MongoDB, clique no banner abaixo e conheça o meu curso em videoaulas sobre o assunto!

Curso Node.js e MongoDB
Curso Node.js e MongoDB

Tutorial CRUD em Node.js com driver nativo do MongoDB – Parte 2

Lista de Clientes com Bootstrap

Atualizado em 31/03/2021!

O tutorial de hoje é uma continuação de um outro tutorial aqui do blog, muito acessado aliás, onde ensino como fazer um sistema de cadastro bem simples em Node.js, usando o web framework ExpressJS e o banco de dados MongoDB. Ou seja, o famoso CRUD.

Nesta segunda parte, parto para um conceitos mais avançados em cima do mesmo projeto: paginação de resultados.

Para conseguir acompanhar todos os códigos, é importante que você tenha realizado a parte anterior ou pelo menos baixe os códigos-fonte que se encontram no formulário ao final do tutorial anterior. Caso prefira assistir um vídeo, o tutorial anterior pode ser resumido com este vídeo abaixo que é uma aula gratuita do meu curso de Node.js e MongoDB. Aproveite!

Neste tutorial você vai ver:

Vamos lá!

Atenção, este mesmo tutorial está disponível em videoaula em meu curso de Node.js e MongoDB.

Paginação com Node.js e MongoDB

Nossa aplicação simplesmente lista todos os documentos da nossa coleção no MongoDB, sem qualquer distinção. Nas primeiras duas etapas deste tutorial vamos restringir isso, primeiramente através de uma paginação de resultados e depois através de uma busca.

Conforme trato em detalhes no artigo Tutorial MongoDB para iniciantes em NoSQL: Parte 2, fazemos paginação no MongoDB usando as funções skip e limit de maneira apropriada.

A função skip indica ao MongoDB que deve-se ignorar um número x de resultados da consulta na hora de retornar do servidor de banco. Já a função limit diz ao Mongo que ele deve retornar apenas um número limitado de documentos, independente se a busca retornaria mais elementos normalmente. Ambas funções devem ser usadas após um find, como veremos a seguir.

A lógica para criar paginação é bem simples: determine um tamanho de página, por exemplo 10 elementos, descubra o total de elementos que a consulta retornaria, por exemplo 21 elementos, e depois dividida o total pelo tamanho de página, arredondando sempre pra cima. Pronto, você tem a quantidade de páginas para aquela consulta! Neste exemplo de 21 elementos com uma paginação de tamanho 10, serão 3 páginas, sendo que as duas primeiras terão exatamente 10 elementos e a última apenas 1.

Entendeu?

Para determinar o tamanho de página ideal para seu sistema você tem de levar em consideração a performance da sua aplicação e a experiência do seu usuário. Muitas páginas com poucos elementos é fácil do banco retornar, mas ruim pro usuário ficar navegando. Poucas páginas com muitos elementos é o oposto e o segredo está no equilíbrio. Neste exemplo, ficaremos com tamanho 10 mesmo, para fins de teste.

Curso FullStack

Paginação de Resultados em Node.js

Então nosso primeiro passo será modificar uma function já existente no módulo db.js da nossa aplicação para que ela retorne os resultados de maneira paginada, como abaixo.

Note que comecei definindo uma constante com o tamanho das páginas sendo 5. Depois, adicionei um novo parâmetro na função findAll que espera a página que a aplicação deseja apresentar. Este parâmetro eu uso para calcular o skip, ou seja, quantos elementos da consulta eu devo ignorar. Se a página for a primeira, o skip será zero e serão mostrados os primeiros 10 elementos. Se a página for a segunda, o skip será 10 pela fórmula e serão mostrados os elementos das posições 11 a 20 (ordinal, ou 10 a 19 considerando um array zero-based).

Agora vamos modificar onde esta função findAll é chamada: na pasta routes, módulo index.js, que vamos modificar a rota GET padrão em dois pontos.

Primeiro, apenas para adicionar o parâmetro página nela. Note que coloquei o parâmetro no path como sendo opcional (?) e se ele não tiver sido passado na URL, será atribuído como um. Esse truque do || para atribuir um valor default eu ensinei no artigo 15 Dicas de JavaScript, lembra?

Certifique-se de que esta rota seja a última do arquivo index.js, logo antes do module.exports!

Como as rotas são testadas da primeira até a última para ver qual que processará a requisição deixaremos esta pro final para não interferir nas demais avaliações.

Agora execute a aplicação e teste a index passando a página na URL, como abaixo. Note que tive de adicionar muitos customers no banco de dados para podermos ter alguma paginação.

Paginação funcionando via URL
Paginação funcionando via URL

Mas obviamente não faremos nosso usuário ter de mudar parâmetros na URL para acessar as páginas, certo?

Está curtindo o post? Para uma formação ainda mais completa como programador web recomendo meu livro sobre programação web com Node.js clicando no banner abaixo!

Paginação de resultados com ExpressJs e EJS

Para que o usuário saiba quantas páginas existem e para que consiga acessar as mesmas, vamos ter de criar uma lógica para construir o HTML de paginação no frontend e uma lógica para retornar algumas informações importantes no backend.

Vamos começar pelo backend que é mais a minha praia. XD

Abra seu arquivo db.js novamente e vamos criar uma function countAll que retorna a quantidade de documentos na coleção customers. Note que atualizei o module.exports com a nova função e também exportando a constante com o tamanho de página.

Agora na routes/index.js, mais especificamente no nosso GET default (a última rota do arquivo), vamos ajustar as chamadas para construir o model com as informações que precisaremos no frontend EJS:

Note que recebo a quantidade de clientes na base através de um await novamente (pois a função countAll é assíncrona, tenho de usar await para esperar seu retorno). A cereja do bolo fica para o cálculo de quantidade de páginas que fizemos ali, dividindo o total de documentos pelo tamanho da página, usando a constante existente no módulo db.js. Ah sim, você não esqueceu de expor esta constante no module.exports, certo?

Agora que nosso model está completo, vamos mexer na index.ejs, nossa view inicial da aplicação para renderizar a quantidade total de elementos e o HTML de paginação para que o usuário possa navegar entre as páginas:

Esse código eu coloquei bem no final do arquivo EJS, onde antes ficava apenas o botão de Cadastrar novo cliente. Se você fez tudo certo até aqui, quando testar novamente você deve ver o seguinte resultado na interface da listagem:

Paginação funcionando
Paginação funcionando

Melhorando usabilidade da paginação

Para encerrar este tutorial, vamos adicionar uma perfumaria para melhorar ainda mais a usabilidade desta página: a página atual não deve ter link, apenas as demais páginas, assim, o usuário saberá em que página ele se encontra atualmente.

Vamos iniciar ajustando o model retornado no nosso index.js para informar também a página atual solicitada pelo usuário:

Note que a mudança foi bem sutil, apenas uma nova propriedade no JSON ‘pagina’.

Para fazer a lógica necessário para que o HTML ora tenha link, ora não tenha, basta adicionar um if na lógica de construção do nosso EJS usando a informação da página oriunda do model:

Ou seja, se a variável de iteração ‘i’ for diferente da página atual, escreve um link HTML na tela, caso contrário, escreve apenas o número da página. Visualmente falando temos a imagem abaixo como referência:

Indicando a página atual
Indicando a página atual

Note como ficou evidente que estamos na página 2, pois não tem link pra ela!

Outras melhorias poderiam incluir legendas quando se passa o mouse sobre as páginas, links de próxima página e página anterior, lógica para quando tivermos muitas páginas (e se tiver 100 páginas, como ficará a tela?) e até mesmo informação textual de que você está vendo os itens x a y da página z.

Mas…isso fica para você pensar meu amigo ou minha amiga! 🙂

No próximo post desta série, você confere como modularizar melhor as suas páginas EJS e como estilizar tudo com Bootstrap. Confira neste link!

Gostou do tutorial? Quer aprender ainda mais sobre Node.js, ExpressJS, EJS e MongoDB comigo? Conheça meu curso de Nodejs e MongoDB clicando no banner abaixo!

Curso Node.js e MongoDB
Curso Node.js e MongoDB

Tutorial CRUD em Node.js com driver nativo do MongoDB

Workshop Node+Mongo no TDC
Workshop Node+Mongo no TDC SP

Atualizado em 29/03/2021! Este post possui aula em vídeo no meu curso de Node.js e MongoDB.

Node.js é uma plataforma que permite a você construir aplicações server-side em JavaScript, já falei disso extensivamente neste artigo.

MongoDB é um banco de dados NoSQL, orientado a documentos JSON(-like), o mesmo formato de dados usado no JavaScript, além de ser utilizado através de funções com sintaxe muito similar ao JS também, diferente dos bancos SQL tradicionais e também já falei disso extensivamente neste artigo.

Vê algo em comum entre os dois? Pois é, ambos tem o JavaScript em comum e isso facilita bastante o uso destas duas tecnologias em conjunto, motivo de ser uma dupla bem frequente para projetos de todos os tamanhos.

A ideia deste tutorial é justamente lhe dar um primeiro contato prático com ambos, fazendo o famoso CRUD (Create, Retrieve, Update e Delete).

Neste artigo você vai ver:

  1. Configurando o Node.js
  2. Configurando o MongoDB
  3. Conectando no MongoDB com Node
  4. Cadastrando no banco
  5. Atualizando clientes
  6. Excluindo clientes

Prefere assistir a vídeos ao invés de ler um tutorial grande? O vídeo abaixo é uma aula gratuita do meu curso de Node.js e MongoDB. Aproveite!

#1 – Configurando o Node.js

Vamos começar instalando o Node.js: 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 (recomendo a versão LTS). Esse executável irá instalar o Node.js e o NPM, que é o gerenciador de pacotes do Node.

Uma vez com o NPM instalado, vamos instalar o módulo que será útil mais pra frente. Crie uma pasta para guardar os seus projetos Node no computador (recomendo C:node) e dentro dela, via terminal de comando com permissão de administrador (sudo no Mac/Linux), rode o comando abaixo:

O Express é o web framework mais famoso da atualidade para Node.js. Com ele você consegue criar aplicações e APIs web muito rápida e facilmente. O que instalamos é o express-generator, um gerador de aplicação de exemplo, que podemo usar via linha de comando, da seguinte maneira:

O “-e” é para usar a view-engine (motor de renderização) EJS, ao invés do tradicional Jade/Pug. Já o “–git” deixa seu projeto preparado para versionamento com Git. Aperte Enter e o projeto será criado (talvez ele peça uma confirmação, apenas digite ‘y’ e confirme).

Depois entre na pasta e mande instalar as dependências com npm install:

Ainda no terminal de linha de comando e, dentro da pasta do projeto, digite:

Isso vai fazer com que a aplicação default inicie sua execução em localhost:3000, que você pode acessar pelo seu navegador.

Capture

Happy Hello World!

Curso FullStack

#2 – Configurando o MongoDB

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.

Vamos agora acessar o site oficial do MongoDB e baixar o Mongo. Clique no menu superior em Software > Community Server e busque a versão do Community Server 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/version, o que é está ok para a maioria dos casos.

Dentro da pasta do seu projeto Node, que aqui chamei de workshoptdc, deve existir uma subpasta de nome data, crie ela agora. Nesta pasta vamos armazenar nossos dados do MongoDB.

Pelo prompt de comando, entre na subpasta bin dentro da pasta de instalação do seu MongoDB e digite (no caso de Mac e Linux, coloque um ./ antes do mongod e ajuste o caminho da pasta data de acordo):

Isso irá iniciar o servidor do Mongo. Se não der nenhum erro no terminal, o MongoDB está pronto e está executando corretamente.

Agora abra outro prompt de comando (o outro ficará executando o servidor) e novamente dentro da pasta bin do Mongo, digite:

Após a conexão funcionar, 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.

Opcionalmente você pode usar ferramentas visuais como o MongoDB Compass, que é gratuita.

Agora, no console do cliente mongo, digite:

Isso vai fazer com que o terminal se conecte exatamente na base “workshoptdc.” 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. Caso não seja o seu caso, terá que buscar algum tutorial de JSON na Internet antes de prosseguir.

Vamos adicionar um registro à nossa coleção (o equivalente do Mongo às tabelas do SQL). Para este tutorial teremos apenas uma base de customers (clientes), sendo o nosso formato de dados como abaixo:

O atributo _id pode ser omitido, neste caso o próprio Mongo gera um id pra você. No seu cliente mongo, digite:

Uma coisa importante aqui: “db” é a base de dados na qual estamos conectados no momento, que um pouco antes havíamos definido como sendo “workshoptdc”. A parte “customers” é o nome da nossa coleção, que passará a existir assim que adicionarmos um objeto JSON nela. Tecle Enter para que o comando seja enviado ao servidor. Se tudo deu certo, uma respostas com “nInserted: 1” (number of inserted) deve aparecer.

Para ver se o registro foi parar no banco realmente, digite:

O pretty() no final do comando find() é para identar o resultado, que retornará:

Claro, o seu _id pode 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, Google!

Agora vamos adicionar mais alguns registros no seu console mongo:

Nesse exemplo passei um array com vários objetos para nossa coleção. Usando novamente o comando db.customers.find().pretty() irá mostrar que todos foram salvos no banco. Agora sim, vamos interagir de verdade com o web server + MongoDB.

Curso Node.js e MongoDB
Curso Node.js e MongoDB

#3 – Conectando no MongoDB com Node

Agora sim vamos juntar as duas tecnologias-alvo deste post!

Precisamos adicionar uma dependência para que o MongoDB funcione com essa aplicação usando o driver nativo. Usaremos o NPM via linha de comando de novo:

Com isso, uma dependência nova será baixada para sua pasta node_modules e uma novas linha de dependência será adicionada no package.json para dar suporte a MongoDB.

Primeiramente, para organizar nosso acesso à dados, vamos criar um novo arquivo chamado db.js na raiz da nossa aplicação Express (workshoptdc). Esse arquivo será o responsável pela conexão e manipulação do nosso banco de dados, usando o driver nativo do MongoDB. Adicione estas linhas:

Estas linhas carregam o objeto mongoClient  a partir do módulo ‘mongodb’ e depois fazem uma conexão em nosso banco de dados localhost, sendo 27017 a porta padrão do MongoDB. Essa conexão é armazenada globalmente, para uso posterior e em caso de erro, o mesmo é logado no console.

A última linha ignore por enquanto, usaremos ela mais tarde. Agora abra o arquivo www que fica na pasta bin do seu projeto Node e adicione a seguinte linha no início dele:

Nesta linha nós estamos carregando o módulo db que acabamos de criar e guardamos o resultado dele em uma variável global. Ao carregarmos o módulo db, acabamos fazendo a conexão com o Mongo e retornamos aquele objeto vazio do module.exports, lembra? Usaremos ele mais tarde, quando possuir mais valor.

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.

Para conseguirmos fazer uma listagem de clientes, o primeiro passo é ter uma função que retorne todos os clientes em nosso módulo db.js (arquivos JS que criamos são chamados de módulos se possuírem um module.exports no final), assim, adicione a seguinte função ao seu db.js (substituindo aquela linha do module.exports que tinha antes):

A consulta aqui é bem direta: usamos a conexão global conn para navegar até a collection de customers e fazer um find sem filtro algum. O resultado desse find é um cursor, então usamos o toArray para convertê-lo para um array e retorná-lo.

Repare na última instrução, ela diz que a nossa função findAll poderá ser usada em outros pontos da nossa aplicação que importem nosso módulo db:

Agora vamos programar a lógica que vai usar esta função. Abra o arquivo ./routes/index.js no seu editor de texto (sugiro o Visual Studio Code).  Dentro dele temos apenas a rota default, que é um get no path raiz. Vamos editar essa rota da seguinte maneira:

router.get define a rota que trata essas requisições com o verbo GET. Quando recebemos um GET /, a função de callback dessa rota (aquela com req, res e next) é disparada e com isso usamos o findAll que acabamos de programar. Como esta é uma função assíncrona que vai no banco de dados, precisamos usar a palavra reservada await antes dela, ter um try/catch para tratar seus possíveis erros e o callback do router tem de ter a palavra async antes dos parâmetros da função.

Foge um pouco do escopo deste tutorial explicar programação assíncrona em Node.js, então recomendo o vídeo abaixo se quiser entender mais do assunto.

Agora vamos arrumar a nossa view para listar os clientes. Entre na pasta ./views/ e edite o arquivo index.ejs para que fique desse jeito:

Aqui estamos dizendo que o objeto docs, que será retornado pela rota que criamos no passo anterior, será iterado com um forEach e seus objetos utilizados um-a-um para compor uma lista não-ordenada com seus nomes.

Isto é o bastante para a listagem funcionar. Para não ter que ficar derrubando e subindo novamente a sua aplicação toda hora, modifique o script de start no seu package.json para:

Agora suba com npm start novamente e toda vez que fizer novas alterações, elas serão carregadas automaticamente por causa da extensão nodemon, que eu falo mais a respeito no vídeo abaixo.

Abra seu navegador, acesse http://localhost:3000/userlist e maravilhe-se com o resultado.

Lista de Clientes
Lista de Clientes

 

Se você viu a página acima é porque sua conexão com o banco de dados está funcionando!

Agora vamos seguir adiante com coisas mais incríveis em nosso projeto de CRUD!

Parte 4 – Cadastrando no banco

Listar dados é moleza e salvar dados no MongoDB 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 new.ejs com o seguinte HTML dentro:

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 new quando acessarmos /new no navegador:

Se você reiniciar seu servidor Node e acessar http://localhost:3000/newuser verá a página abaixo:

Novo Cadastro
Novo Cadastro

 

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!.

Então, vamos alterar nosso db.js para incluir uma nova função, desta vez para inserir clientes usando a conexão global e, novamente, incluindo esta função no module.exports:

Agora vamos criar uma rota para que, quando acessada via POST, nós chamaremos o objeto global db para salvar os dados no Mongo. A rota será a mesma /new, porém com o verbo POST. 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:

Obviamente no mundo real você irá querer colocar validações, mas aqui, apenas pego os dados que foram postados no body da requisição HTTP usando o objeto req (request/requisição). Crio um JSON com essas duas variáveis e envio para função insert que criamos agora a pouco.

Novamente, idas ao banco são assíncronas no Node.js, então isso exige o uso de async na função que a chamada estiver, o uso de await para recebermos o seu retorno e um try/catch para tratar erros. Se tudo der certo, a rota redireciona para a index novamente para que vejamos a lista atualizada. Apenas para finalizar, edite o views/index.ejs para incluir um link para a página /new:

Resultado:

Cadastro funcionando
Cadastro funcionando

 

#5 – Atualizando clientes

Para atualizar clientes (o U do CRUD) não é preciso muito esforço diferente do que estamos fazendo até agora. No entanto, são várias coisas menores que precisam ser feitas e é bom tomar cuidado para não deixar nada pra trás.

Primeiro, vamos editar nossa views/index.ejs para que quando clicarmos no nome do cliente, joguemos ele para uma tela de edição. Fazemos isso com uma âncora ao redor do nome, construída no EJS:

Note que este link aponta para uma rota /edit que ainda não possuímos, e que após a rota ele adiciona o _id do customer, que servirá para identificá-lo na página seguinte. Com esse _id em mãos, teremos de fazer uma consulta no banco para carregar seus dados no formulário permitindo um posterior update. Por ora, apenas mudou a aparência da tela de listagem:

Listagem com Edição
Listagem com Edição

Sendo assim, vamos começar criando uma nova função no db.js que retorna apenas um cliente, baseado em seu _id:

Como nosso filtro do find será o id, ele deve ser convertido para ObjectId, pois virá como string na URL e o Mongo não entende Strings como _ids. Não esqueça de incluir esta nova função no module.exports, que está crescendo.

Agora vamos criar a respectiva rota GET em nosso routes/index.js que carregará os dados do cliente para edição no mesmo formulário de cadastro:

Esta rota está um pouco mais complexa que o normal. Aqui nós pedimos ao db que encontre o cliente cujo id veio como parâmetro da requisição (req.params.id). Após ele encontrar o dito cujo, mandamos renderizar a mesma view de cadastro, porém com um model inteiramente novo contendo apenas um documento (o cliente a ser editado) e a action do form da view ‘new.ejs’.

Mas antes de editar a view new.ejs, vamos editar a rota GET em /new para incluir no model dela o cliente e a action, mantendo a uniformidade entre as respostas:

Agora sim, vamos na new.ejs e vamos editá-la para preencher os campos do formulário com o model recebido, bem como configurar o form com o mesmo model:

Agora, se você mandar rodar e clicar em um link de edição, deve ir para a tela de cadastro mas com os campos preenchidos com os dados daquele cliente em questão:

Edição quase funcionando
Edição quase funcionando

Agora estamos muito perto de terminar a edição. Primeiro precisamos criar um nova função no db.js para fazer update, como abaixo:

O processo não é muito diferente do insert, apenas temos de passar o filtro do update para saber qual documento será afetado (neste caso somente aquele que possui o id específico). Também já inclui o module.exports atualizado na última linha para que você não esqueça.

Para encerrar, apenas precisamos configurar uma rota para receber o POST em /edit com o id do cliente que está sendo editado, chamando a função que acabamos de criar:

Aqui carreguei o id que veio como parâmetro na URL, e os dados de nome e idade no body da requisição (pois foram postados via formulário). Apenas passei esses dados nas posições corretas da função de update e passei um callback bem simples parecido com os anteriores, mas que redireciona o usuário para a index do projeto em caso de sucesso, voltando à listagem.

E com isso finalizamos o update!

#6 – Excluindo clientes

Agora em nossa última parte do tutorial, faremos o D do CRUD, D de Delete!

Vamos voltar à listagem e adicionar um link específico para exclusão, logo ao lado do nome de cada cliente, incluindo uma confirmação de exclusão nele via JavaScript, como abaixo:

Note que não sou muito bom de front-end, então sinta-se à vontade para melhorar os layouts sugeridos. Aqui, o link de cada cliente aponta para uma rota /delete passando _id do mesmo. Essa rota será acessada via GET, afinal é um link, e devemos configurar isso mais tarde.

Exclusão funcionando
Exclusão funcionando

Vamos no db.js adicionar nossa última função, de delete:

Essa função é bem simples de entender se você fez todas as operações anteriores. Na sequência, vamos criar a rota GET /delete no routes/index.js:

Nessa rota, após excluirmos o cliente usando a função da variável global.db, redirecionamos o usuário de volta à tela de listagem para que a mesma se mostre atualizada.

E com isso finalizamos nosso CRUD!

A continuação deste tutorial você confere neste post e as videoaulas estão neste curso.

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!