Upload de imagens no S3 da AWS com Node.js
✅ Pré-requisitos
Sempre queremos entregar uma melhor experiência para nossa audiência.
Para continuar com a leitura, certifique-se de cumprir os seguintes requisitos:
- Conhecimento intermediário em JavaScript (Promises e Classes);
- Conhecimento Básico em Node.js (path, fs) | npm ou yarn para adicionar libs e executar o projeto;
- Insomnia ou Postman instalados — para testar as rotas;
- MongoDB instalado, ou ter uma conta no MongoDB Atlas
- MongoDB Compass ou Robo 3T instalado;
- Opcional: Conta na AWS para criar um S3, vai ser obrigatório se quiser testar em seu próprio bucket, caso contrário testando localmente já dá para continuar a leitura.
🔰 Introdução
Em 10 passos vamos implementar uma API em Node.js que irá fazer upload de imagens em uma pasta temporária no servidor local e também no serviço S3 (Simple Storage Service) da AWS (Amazon Web Services). Iremos utilizar o MongoDB para armazenar os Posts que contém informações referente a imagem que será salva. Vamos disponibilizar as rotas para que o front end consuma a API.
S3 da AWS é um serviço altamente escalável e barato, que nos motiva utiliza-lo para arquivos estáticos. Custa menos dinheiro colocar arquivos no S3 do que no servidor em uma pasta temporária que consome o disco — Heroku, Digital Ocean, Umbler, entre outros.
Todavia, para um app de teste, claro que compensa manter nesses serviços.
Hoje já vamos aprender como usar S3, pode ser necessário para apps profissionais.
👨💻 Bora codar
Vou adicionar os códigos e explicar passo a passo:
Passo 1 - Criando o arquivo package.json
Crie uma pasta no seu workspace chamada backend-upload
, abra no seu Editor de códigos — VsCode, e crie o arquivo package.json
com a configuração abaixo:
Dependências em produção:
aws-sdk
= kit de desenvolvimento da AWS, api que utilizamos para lidar com seus serviços;dotenv
= lib que carrega as variáveis de ambiente para aplicação — arquivo.env
;express
= servidor WEB que recebe requisições HTTP;mongoose
= Object Data Modeling (ODM) faz o mapeamento do Objeto (Schema) JavaScript em uma Coleção no MongoDB;morgan
= middleware que faz o log de requisições HTTP de uma maneira mais amigável para o desenvolvedor;multer
= lib que lida com upload de arquivos;multer-s3
= lib que lida com upload de arquivos no S3/AWS.
Dependências em ambiente de desenvolvimento:
nodemon
= para fazer o reload da aplicação assim que é feita uma alteração no código, exceto arquivos.env
.cors
= habilita o front end consumir nosso back end (API) que estamos construindo.
scripts:
dev
= vai ser utilizando apenas em desenvolvimento para o nodemon monitorar as atualizações e fazer reload do app;start
= vai ser utilizado em produção, para rodar a aplicação.
Passo 2 - Instalando as dependências
Execute um dos comandos comando abaixo:
npm install
// ou
yarn install
Após a execução do comando, a pasta node_modules
que carrega todas as dependências definidas no package.json
deve ficar na raiz no seu projeto.
Passo 3 - Criando a variável de ambiente
Crie o arquivo .env
e .env-sample
na raiz do projeto:
Ambos terão os mesmos conteúdo, porém, você vai preencher conforme as suas credenciais da Amazon que você obterá no Passo 8, por enquanto deixe assim:
.env
e .env-sample
:
# Se hospedar sua aplicação em algum lugar
# coloque o host aqui:
APP_URL=http://localhost:3000
# s3: para produção
# local: em desenvolvimento
STORAGE_TYPE=local
# Pode ser da sua máquina local ou Mongo Atlas
MONGO_URL=mongodb://localhost:27017/upload
# Pegue esses dados no site S3 AWS
BUCKET_NAME=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
O Arquivo .env
nunca deve ser enviado para o controle de versão (git), por isso existe o arquivo .env-sample
que será enviado somente com as propriedades definidas, sem os valores preenchidos (a menos que os dados não sejam sensíveis).
Passo 4 - Criando o Servidor
Crie uma pasta src
na raiz do projeto e dentro dela o arquivo index.js
:
src/index.js:
Criando o servidor com Express no Node.js
1 - Carregando para aplicação as variáveis de ambiente;
3 a 7 - Importando os arquivos necessários para criar o servidor;
9 - Criando a variável app
que irá conter uma instância do servidor express;
14 a 19 - Criando a conexão com mongoose;
21 -Adicionando o middleware cors
, para habilitar à API ser acessada de outra origem como um front end hospedado em outra origem;
22 - Permitindo o servidor express
lidar com JSON no body da requisição.
23 - Permitindo o servidor lidar com requisições no padrão URL Encode para facilitar o envio de arquivos;
24 - Utilizando o Morgan no formato dev
para fazer o logging das requisições HTTP, de maneira que o(a) dev consiga ler no terminal e entender o que está sendo requisitado — rota, parâmetros e corpo da requisição;
Exemplo:
my/user/workspace/backend-upload
POST /posts 200 1364.073 ms - 315
POST /posts 200 4152.602 ms - 272
GET /posts 200 20.996 ms - 1537
DELETE /posts/5f5bb5d24341822ebff17f2c 200 799.458 ms - -
25 a 28 - Expondo a rota files
para servir arquivos estáticos, path
é responsável para acessar o caminho na pasta do servidor, __dirname
é o diretório onde o path está sendo executado;
30 - Importa o arquivo de rotas que vamos criar logo em seguida e utiliza como um middleware que recebe as requisições;
32 - Inicia o servidor e disponibiliza a aplicação na porta 3000
.
Passo 5 - Criando as Rotas da aplicação
Crie o arquivo routes.js
dentro da pasta src
:
src/routes.js
1 - Importa o roteador do express
— Router. E atribui na constante router
.
2 - Importa o multer
que lida com upload de arquivos;
3 - Importa a configuração do multer
que iremos criar em seguida;
5 - Importa o model Post
que é a representação de um Post do Objeto JavaScript para o mongoDB. Vamos criar o model Post em seguida;
7 a 10 - Criamos uma rota /post
que recebe as requisições GET que irá buscar todos os posts incluídos na base de dados do mongodb
na coleção Post
e retorna via JSON um array de posts.
13 a 24 - Criamos uma rota /post
que recebe as requisições POST, ela irá receber o arquivo, usando o multer
com as configurações. Irá receber um arquivo por vez (single) da requisição no parâmetro file
. Será pego os dados da imagem quem vem no atributo file
: nome, tamanho do arquivo, nome do arquivo (key) e a URL, com esses dados em mãos iremos criar um Post
no banco de dados. Por fim será retornado o JSON com os dados do Post.
26 a 32 - Criamos uma rota /post/:id
que que recebe requisições DELETE com um parâmetro id
informado na rota. Buscamos o Post
pelo ID, removemos usando o await post.remove()
e retornamos um status 200.
34 - exportamos esse módulo de rotas que foi importado no arquivo index.js
.
Passo 6 - Configurando o Multer para salvar imagem no disco e no S3
Crie a pasta config
dentro da pasta src
. Nela irá conter o arquivo multer.js
com as configurações para salvar imagens na pasta temporária: tmp/uploads
e também no bucket
do S3.
src/config/multer.js
1 a 5 - Importamos os módulos necessários para lidar com os arquivos enviados na rota /posts
via método POST. Tanto os arquivos que serão salvos no servidor local quanto no servidor no S3 da AWS.
7 - Definimos a constante que armazena o valor máximo (2 MB) permitido para uploads de arquivo, abstraindo a conta matemática que converte 2 Megabytes em um valor inteiro, resolvemos o problema de números mágicos, seguindo uma boa prática do Clean Code 😀
9 a 39 - Criamos um objeto storageTypes
com duas propriedades: local
e s3
.
Na propriedade local
, utilizamos o multer
para para salvar no disco (servidor local) usando a função diskStorage
que recebe a propriedade destination
que recebe uma função com os três parâmetros (req, file, callback)
chamamos o cb
passando null
para o erro, e o caminho de onde vai ser salvo o arquivo, nesse caso em uma pasta tmp/uploads
na raiz do projeto.
A propriedade filename
é uma função que recebe os mesmos parâmetros da função anterior, implementamos uma lógica para gerar o nome do arquivo usando um hash
para representar o nome do arquivo mais o nome original da imagem. No final é chamada a função cb
com dois parâmetros, o erro é null
e o segundo é o nome do arquivo: file.key
.
Já na propriedade s3
é feita a configuração para salvar os dados no serviço S3. Usando a função multerS3
passamos as configurações:
s3
= recebe uma instância doaws.S3
bucket
= nome dobucket
que criamos no site da S3;contentType
= serve para abrir a imagem no navegador ao invés de forçar o download;acl
= são as permissões,public-read
significa que nossa API pode ter acesso ao bucket;key
= nome da imagem.
41 a 61 - exportamos o módulo multerConfig
passando as propriedades:
dest
= caminho para a pasta uploads onde são salvos arquivos temporariamente;storage
= onde serão salvos os arquivos, pode ser local ou s3, depende do que foi configurado na variável de ambienteSTORAGE_TYPE
em ambiente de dev:local
e ambiente de produçãos3
;limits
recebe o objetofileSize
com o valor inteiro do tamanho máximo permitido para uploads de arquivo;fileFilter
função que filtra os tipos de arquivos que serão aceitos, se o arquivo enviado não estiver incluído no array de arquivos permitidos é lançado um erro para o usuário caso contrário é permitido e continua a operação.
Passo 7 - Criando o Model Post
Dentro de src
crie a pasta models
com arquivo Post.js
PostSchema
será a entidade que representará um Post
tanto no Objeto JavaScript quando na coleção do MongoDB. O Mongoose irá fazer essa intermediação, mapeando a relação Objeto ↔ Documento e fornecendo interfaces para um CRUD completo.
src/models/Post.js
1 a 5 - Importamos as bibliotecas necessárias para criar o Schema Post
usando mongoose
, e a outras bibliotecas que irão lidar com o arquivo para gerar um link completo e deletar as imagens.
7 - Criamos uma constante s3
que recebe uma instância da biblioteca S3 da AWS;
9 a 18 - Definimos o Schema
do Post
, com os atributos necessários com seus respectivos tipos de dados. Mongoose (ODM) vai fazer com que esse objeto seja compatível com a MongoDB.
20 a 24 - Criamos um lógica que antes (pre
) que for salvo algum documento no MongoDB essa função será chamada. Ela basicamente utiliza o this
referente ao PostSchema
para adicionar na url
o endereço do servidor representado pela variável de ambiente APP_URL
mais /files/
e o nome do arquivo que está na variável this.key
:
Representação:
http://localhost:3000/files/c8a934791778f64650c57dd3c94d8d47-minhafoto.png
26 a 45 - Criamos um lógica antes (pre
) que for chamada alguma função de deleção de documentos no MongoDB, a função seja executada.
Se estiver no ambiente STORAGE_TYPE = "s3"
a lógica do if
é executada, deletando do bucket
definido no BUCKET_NAME
o arquivo (key) this.key
.
Se não estiver no ambiente da S3 então é removido o arquivo do servidor local, usando o file system (fs
) do Node.js que vai fazer unlink
para remover o arquivo. É utilizado o promisify
do Node.js para deixar o código em formato de promises tornando mais elegante e não sendo necessário lidar com callbacks. Para dentro da promise do unlink
vai o caminho resolvido via path.resolve
do arquivo no servidor.
Passo 8 - Criando um Bucket no S3 da AWS
Deixei os vídeos no momento exato onde começa a falar sobre assunto:
- Crie um Bucket seguindo os mesmos passos do vídeo.
- Depois crie um usuário no Identity and Access Management (IAM) da AWS.
- Dê permissão ao acesso público do bucket (ACL):
Pronto, agora já podemos continuar.
Passo 9 - Iniciando o MongoDB e conferindo as variáveis de ambiente
Antes de iniciar o App, você precisa estar com o MongoDB rodando na máquina, utilize o Docker ou MongoAltas.
Se você não tiver o MongoDB no seu Docker, crie um container usando esse comando:
docker run --name mongo -p 27017:27017 -d -t mongo
Confira se o mongo já está executando:
docker ps
Para saber se o mongo está funcionando, acesse a URL: http://localhost:27017/
Resultado esperado:
It looks like you are trying to access MongoDB over HTTP on the native driver port.
Utilizando o MongoDB Compass ou Robo 3T, crie um banco de dados chamado upload
e uma coleção Post
.
Por fim verifique se as variáveis de ambiente estão preenchidas:
.env:
# Se hospedar sua aplicação em algum lugar
# coloque o host aqui:
APP_URL=http://localhost:3000
# s3 para produção
# local em desenvolvimento
STORAGE_TYPE=s3
# Pode ser da sua máquina local ou Mongo Atlas
MONGO_URL=mongodb://localhost:27017/upload
# Pegue esses dados no site S3 AWS
BUCKET_NAME=tutorialuploadfile
AWS_ACCESS_KEY_ID=123JH12K3H1KJ3K1
AWS_SECRET_ACCESS_KEY=123JK12H3JK1+23232321AASD+ggn4qbkgyCI
AWS_DEFAULT_REGION=us-east-1
Se for testar no S3 mantenha o STORAGE_TYPE=s3
, senão, deixe STORAGE_TYPE=local
Recomendo testar primeiro localmente.
Passo 10 - Iniciando o servidor e fazendo requisições com Insomnia
Para iniciar a aplicação basta executar o comando:
yarn dev
// ou
npm dev
Abra o Insomnia e crie três rotas:
POST http://localhost:3000/posts
GET http://localhost:3000/posts
DELETE http://localhost:3000/posts/5f5bb5d24341822ebff17f2c
Tente criar, buscar e deletar o Post.
Teste fazer o upload tanto na máquina local quando no S3.
Veja as imagens:
Imagem que fiz upload no bucket do S3:
Pronto! App finalizado 👏 👊 💪
Qualquer dúvida pergunte aqui nos comentários ou na comunidade do Discord!
👍 Conclusão
Com poucos arquivos e poucas linhas de código, conseguimos criar um servidor web, expor três rotas para listar, deletar e criar posts. Fazer upload de imagens tanto em disco local quanto no S3 da AWS.
Se ficou alguma dúvida, o Diego Fernandes fez um vídeo explicando como fazer upload de imagens no back end Node.js, confere lá:
E aí, o que achou do post?
Espero que tenha curtido! 💜
O aprendizado é contínuo e sempre haverá um próximo nível! 🚀