{"componentChunkName":"component---src-templates-post-item-js","path":"/blog/upload-e-compressao-de-imagens-com-nodejs/","result":{"data":{"markdownRemark":{"html":"<p>Fala minha gente! É com muito prazer que escrevo esse artigo tentando resolver um problema que tenho visto nos forums assolar a vida de muitos programadores: como fazer o upload de imagens, e principalmente: como comprimi-las e tratá-las após recebe-las no servidor. Ficou um artigo um pouco longo pois nele desenvolvo um passo-a-passo bem explicado, mas continua que vale a pena!</p>\n<p>Para prosseguir, vamos utilizar <strong>duas bibliotecas:</strong> o <strong><em>Multer</em></strong>, para o <strong>upload de imagens, e o Sharp, para processar essas imagens(redimensionar, comprimir, converter…).</strong> Ambos são <strong>middlewares</strong> para utilizar com <strong>NodeJS</strong>, e nesse artigo, utilizaremos com <strong>Express</strong>, embora funcionem sem.</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 960px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/166a3/node-compressao.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 25.416666666666664%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAIAAADKYVtkAAAACXBIWXMAAAsTAAALEwEAmpwYAAABH0lEQVQY0yWPT0+CYACH/YD6vi8CYrlpm5tdWvkFKgMEghdBZ1kdmpt9htYt1xnlryIyUIxWB8+dA9uePbdn+/0K0LiBJgdtvhiIKFJhjHMihYjVcqwSawWtZWB3gcUBkwVzFsw6wLiGxhU0LgtwxgKTIxzh/HNMB+Lp5qG/f2vtHmvpXXU7oEOVjrTKSqVtKYO0RMoUqDkPD30BmnzJ4hhHnuw/Gu9t7D1Pf4Phz2szfbr4mrS/X87ScSscNf3hyUKvOWrd7dVtnO/9j6EnZgZLkQzl2oyrbvRKoh8ng6Ntv7Eb1ZN7FNwWfbG0yAGuAGwezVlodA6xI2RGrpD/jHA50citRuXuERuMYkysZLSQkCfBpUT5mPF7jKeQVvcPNdOtWnacpeUAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image lazyload\"\n        alt=\"Título do artigo com o logo da linguagem à esquerda\"\n        title=\"Título do artigo com o logo da linguagem à esquerda\"\n        data-src=\"/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/d9199/node-compressao.png\"\n        data-srcset=\"/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/8ff5a/node-compressao.png 240w,\n/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/e85cb/node-compressao.png 480w,\n/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/d9199/node-compressao.png 960w,\n/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/07a9c/node-compressao.png 1440w,\n/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/29114/node-compressao.png 1920w,\n/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/166a3/node-compressao.png 1980w\"\n        sizes=\"(max-width: 960px) 100vw, 960px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n      />\n  </a>\n    </span></p>\n<h2>Como estas bibliotecas Funcionam</h2>\n<h3>Multer</h3>\n<p>O <strong>Multer</strong> funciona da seguinte forma: Além das <strong>configurações</strong> <em>(item 3)</em>, nós apontamos <strong>que dados da requisição (os dados recebidos do formulário)</strong> são os arquivos que queremos tratar. Logo, na execução dessa rota (geralmente um <strong>POST</strong>), ele vai <strong>interceptar esses campos</strong>, <strong>tratar esses arquivos, salva-los, e nos retornar na requisição as informações desse armazenamento</strong>, se teve ou não <strong>sucesso</strong>, e se teve, qual as <strong>propriedades desse arquivo salvo</strong>.</p>\n<h3>Sharp</h3>\n<p>O <strong>Sharp</strong> por sua vez, é uma <strong>biblioteca para processar imagens no servidor</strong>. Ele não foi feito exatamente pra ser usado com o <strong>Multer</strong>, mas vamos dar um jeito pra deixar tudo automatizado, e <strong>no momento desse upload, se houve sucesso no armazenamento, simplesmente processamos esse arquivo que foi salvo</strong>.</p>\n<p>Enfim, bora para a parte prática!</p>\n<h2>1. Instalando os pacotes</h2>\n<p>Partindo do princípio que já temos o nosso <strong>servidor Express</strong> basicamente configurado, vamos instalar os nossos <strong>pacotes</strong>, executando o seguinte <strong>comando</strong> no terminal:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">npm i sharp multer</code></pre></div>\n<p>Agora que temos as bibliotecas disponíveis no <strong>projeto</strong>, vou criar um arquivo chamado <strong><em>multer.js</em></strong>, nele vamos <strong>exportar</strong> o nosso <strong>middleware</strong>, para posteriormente <strong>usar nas nossas rotas</strong>. Pouca gente faz isso, mas quando possível é interessante, não só para <strong>economizar código</strong>, mas também <strong>concentrar a lógica importante em um lugar só</strong>. Esse arquivo vai ter o seguinte esqueleto:</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> multer <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'multer'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Vamos exportar nosso módulo multer, executando com as nossas configurações em um objeto.</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token function\">multer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<h2>2. Configurando o Multer</h2>\n<p>Como falei acima, vamos instanciar nosso <strong>middleware</strong> executando a biblioteca com as nossas próprias configurações. Primeiro, vamos apontar as <strong>informações ligadas ao armazenamento</strong>, como o <strong>destino desses arquivos e como eles irão se chamar.</strong> Essas opções vão na propiedade <strong>storage</strong> do nosso objeto. Também podemos configurar um <strong>validador</strong>, para aceitar apenas certos tipos de extensões, na nossa propriedade <strong><em>fileFilter</em></strong>.</p>\n<h3>storage</h3>\n<p>Como queremos <strong>salvar arquivos no disco</strong>, vamos executar utilizar a função <strong><em>diskStorage</em></strong>, disponibilizada pela biblioteca, que vai fazer a <strong>lógica do armazenamento</strong>, basta configurarmos <strong>duas propiedades</strong> que recebem <strong>funções</strong>, e executar o <strong><em>callback</em></strong> que vem em seus <strong>parâmetros</strong> corretamente.</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> multer <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'multer'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Vamos expotar nosso módulo multer, o qual vamos executar passando as nossas configurações em um objeto.</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token function\">multer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    \n     <span class=\"token comment\">// Como deve ser feito o armazenamento dos arquivos?</span>\n    <span class=\"token literal-property property\">storage</span><span class=\"token operator\">:</span> multer<span class=\"token punctuation\">.</span><span class=\"token function\">diskStorage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      \n        <span class=\"token comment\">// Qual deve ser o destino deles?</span>\n        <span class=\"token function-variable function\">destination</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> file<span class=\"token punctuation\">,</span> cb</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            \n            <span class=\"token comment\">// Setamos o destino como segundo paramêtro do callback</span>\n            <span class=\"token function\">cb</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'./app/public/images'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        \n        <span class=\"token comment\">// E como devem se chamar?</span>\n        <span class=\"token function-variable function\">filename</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> file<span class=\"token punctuation\">,</span> cb</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            \n            <span class=\"token comment\">// Setamos o nome do arquivo que vai ser salvado no segundo paramêtro</span>\n            <span class=\"token comment\">// Apenas concatenei a data atual com o nome original do arquivo, que a biblioteca nos disponibiliza.</span>\n            <span class=\"token function\">cb</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> Date<span class=\"token punctuation\">.</span><span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token string\">'-'</span> <span class=\"token operator\">+</span> file<span class=\"token punctuation\">.</span>originalname<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            \n        <span class=\"token punctuation\">}</span>\n        \n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// FIM DA CONFIGURAÇÃO DE ARMAZENAMENTO</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Como você pode ver, a <strong>configuração de armazenamento</strong> tem duas <strong>propriedades</strong>: <strong><em>destination</em></strong> e <strong><em>filename</em></strong>, que vão apontar o <strong>destino dos arquivos e o nome deles</strong>, respectivamente. Elas recebem funções, <strong>onde teremos acesso aos parâmetros:</strong></p>\n<ul>\n<li><strong>req:</strong> a requisição que foi feita aquela rota (normal do express);</li>\n<li><strong>file:</strong> algumas informações do arquivo que foi recebido (nome, tipo, etc);</li>\n<li><strong>cb:</strong> callback que executaremos com a resposta(no seu segundo parâmetro).</li>\n</ul>\n<h3>fileFilter</h3>\n<p>Essa configuração é a responsável pela <strong>validação</strong> do arquivo. Se a validação <strong>falhar</strong>, vamos receber um <strong>objeto <em>undefined</em></strong> na requisição do nosso <em>controller</em>, e se o arquivo for <strong>aprovado</strong>, ele vai ser <strong>armazenado</strong> e receberemos na requisição <strong>um objeto com as informações dele e do armazenamento que foi realizado</strong>.</p>\n<p>Enfim, para usar essa opção basta indicar a propriedade <strong><em>fileFilter</em></strong> no nosso objeto de opções. *Essa propriedade recebe uma função com os <strong>mesmos parâmetros</strong> que vimos nas propriedades <strong>destination e filename</strong>.*</p>\n<p>Lembra que essa função nos da acesso, por meio dos parâmetros, a informações sobre o arquivo que recebemos? Esse objeto tem a propriedade <strong><em>mimetype</em></strong>, que é o <strong>tipo(extensão?) do arquivo</strong>. Basta testarmos se essa propriedade se encaixa com os formatos que esperamos, e baseado nisso, <strong>executamos</strong> o <strong><em>callback</em></strong>, passando <strong><em>false</em> para rejeitado</strong>, e <strong><em>true</em> para aceito</strong>.</p>\n<p>Abaixo você encontra a nossa propiedade <strong><em>fileFilter</em></strong>, com comentários indicando o que está acontecendo, e o nosso objeto de opções <strong>finalizado</strong>.</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> multer <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'multer'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n <span class=\"token comment\">// Os objetos e suas funções são automaticamentes executadas pela biblioteca, no momento do Upload.</span>\n <span class=\"token comment\">// Nessas funções, teremos acesso a requisição, a alguns dados do arquivo, e um callback que vamos</span>\n\n<span class=\"token comment\">// Vamos expotar nosso módulo multer, que vamos executar passando as nossas configurações</span>\nmodule<span class=\"token punctuation\">.</span>exports <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token function\">multer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  \n    <span class=\"token comment\">// Como deve ser feito o armazenamento dos arquivos?</span>\n    <span class=\"token literal-property property\">storage</span><span class=\"token operator\">:</span> multer<span class=\"token punctuation\">.</span><span class=\"token function\">diskStorage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n      \n        <span class=\"token comment\">// Qual deve ser o destino deles?</span>\n        <span class=\"token function-variable function\">destination</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> file<span class=\"token punctuation\">,</span> cb</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n         \n            <span class=\"token comment\">// Setamos o destino como segundo paramêtro do callback</span>\n            <span class=\"token function\">cb</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'./app/public/images'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        \n        <span class=\"token comment\">// E como devem se chamar?</span>\n        <span class=\"token function-variable function\">filename</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> file<span class=\"token punctuation\">,</span> cb</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n          \n            <span class=\"token comment\">// Setamos o nome do arquivo que vai ser salvado no segundo paramêtro</span>\n            <span class=\"token comment\">// Apenas concatenei a data atual com o nome original do arquivo, que a biblioteca nos disponibiliza.</span>\n            <span class=\"token function\">cb</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> Date<span class=\"token punctuation\">.</span><span class=\"token function\">now</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token string\">'-'</span> <span class=\"token operator\">+</span> file<span class=\"token punctuation\">.</span>originalname<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token comment\">// Como esses arquivos serão filtrados, quais formatos são aceitos/esperados?</span>\n    <span class=\"token function-variable function\">fileFilter</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> file<span class=\"token punctuation\">,</span> cb</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n     \n            <span class=\"token comment\">// Procurando o formato do arquivo em um array com formatos aceitos</span>\n      \t     <span class=\"token comment\">// A função vai testar se algum dos formatos aceitos do ARRAY é igual ao formato do arquivo.</span>\n            <span class=\"token keyword\">const</span> isAccepted <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">'image/png'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'image/jpg'</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'image/jpeg'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">find</span><span class=\"token punctuation\">(</span> <span class=\"token parameter\">formatoAceito</span> <span class=\"token operator\">=></span> formatoAceito <span class=\"token operator\">==</span> file<span class=\"token punctuation\">.</span>mimetype <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token comment\">// O formato do arquivo bateu com algum aceito?</span>\n            <span class=\"token keyword\">if</span><span class=\"token punctuation\">(</span>isAccepted<span class=\"token punctuation\">)</span><span class=\"token punctuation\">{</span>\n                <span class=\"token comment\">// Executamos o callback com o segundo argumento true (validação aceita)</span>\n                <span class=\"token keyword\">return</span> <span class=\"token function\">cb</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n            \n            <span class=\"token comment\">// Se o arquivo não bateu com nenhum aceito, executamos o callback com o segundo valor false (validação falhouo)</span>\n            <span class=\"token keyword\">return</span> <span class=\"token function\">cb</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">null</span><span class=\"token punctuation\">,</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<blockquote>\n<p>Geralmente as pessoas usam uma cadeia de ifs para testar o mimetype, mas eu prefiro essa forma automatizada que pensei, assim é só adicionar no array os formatos que queremos.</p>\n</blockquote>\n<p><strong>Vamos aplicar isso nas nossas rotas?</strong></p>\n<h2>3. Multer em ação!</h2>\n<p>Chegou a hora de usar o nosso <strong>middleware</strong> previamente <strong>configurado</strong>. Vou usar <strong>duas rotas</strong> simples, uma que <strong>renderiza o formulário</strong> com <strong>GET</strong> e outra que <strong>recebe o POST</strong> desse formulário. <em>As rotas são apenas para propósitos educativos, então estão bem simples sem padronização MVC, por exemplo.</em></p>\n<h3>Nosso GET:</h3>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// IMPORTAMOS NOSSO MIDDLEWARE</span>\n<span class=\"token keyword\">const</span> multer <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'./app/middleware/multer'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// ROTA PARA GET, RENDERIZAR O FORMULÁRIO</span>\napp<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'/nova-imagem'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">,</span> next</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    res<span class=\"token punctuation\">.</span><span class=\"token function\">send</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n        &lt;html>\n            &lt;head> \n                &lt;title> Nova imagem &lt;/title>\n            &lt;/head>\n            &lt;/body>\n                &lt;!-- O enctype é de extrema importância! Não funciona sem! -->\n                &lt;form action=\"/nova-imagem\"  method=\"POST\" enctype=\"multipart/form-data\">\n                    &lt;!-- O NAME do input deve ser exatamente igual ao especificado na rota -->\n                    &lt;input type=\"file\" name=\"image\">\n                    &lt;button type=\"submit\"> Enviar &lt;/button>\n                &lt;/form>\n            &lt;/body>\n        &lt;/html>\n    </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Dois <strong>detalhes</strong> que ressaltei no código, os quais são <strong>MUITO importantes</strong>, causando a maioria dos erros:</p>\n<ul>\n<li>O formulário deve ter o atributo <strong><em>enctype=”multipart/form-data”</em></strong></li>\n<li>O <strong><em>name</em></strong> da entrada que queremos armazenar deve ser <strong>exatamente igual</strong> ao que especificamos na nossa rota (você já vai entender).</li>\n</ul>\n<h3>E O POST:</h3>\n<p>No nosso <strong>POST</strong>, nós vamos executar também como um <strong>middleware</strong>d a rota, o nosso <strong><em>multer</em></strong>, <strong>indicando a ele o name do form que queremos tratar</strong>. Como queremos <strong>apenas um campo</strong>, vamos usar o método <strong>single</strong>, enviando como parâmetro o <strong>name</strong> do campo que o <strong>middleware</strong> deve agir. <em>Existem outras formas, como usar mais de um name, que você encontra na <a href=\"https://github.com/expressjs/multer\">documentação do Multer</a>.</em></p>\n<blockquote>\n<p>Caso você esteja iniciando no Node/Express: em uma rota, nós podemos passar como parâmetros vários middlewares, que são funções/funcionalidades que processam a nossa requisição, sendo executadas na ordem apontada (explicando de maneira hypermega minimalista).</p>\n</blockquote>\n<p>O <strong>Multer</strong>, que é executado antes do <strong>middleware</strong> principal, vai filtrar a nossa requisição, <strong>procurar pelo campo indicado e tratar ele</strong>. A <strong>saída</strong> desse campo tratado vai ser armazenada na propiedade <strong><em>file</em> da nossa requisição</strong>. <em>O restante dos campos, se houver, vai normalmente para o req.body, como se fosse um form qualquer.</em></p>\n<p><em>Obs: Esse bloco de código está logo embaixo do que mostrei acima, não coloquei um em baixo do outro para não ficar muito grande.</em></p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">// ROTA PARA POST, TRATAR O FORMULÁRIO</span>\n<span class=\"token comment\">// APLICAMOS O NOSSO MIDDLEWARE IMPORTADO PASSANDO O NAME DO INPUT A SER TRATADO</span>\napp<span class=\"token punctuation\">.</span><span class=\"token function\">post</span><span class=\"token punctuation\">(</span><span class=\"token string\">'/nova-imagem'</span><span class=\"token punctuation\">,</span> multer<span class=\"token punctuation\">.</span><span class=\"token function\">single</span><span class=\"token punctuation\">(</span><span class=\"token string\">'image'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">,</span> next</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\n    <span class=\"token comment\">// Se houve sucesso no armazenamento</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">.</span>file<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token comment\">// Vamos imprimir na tela o objeto com os dados do arquivo armazenado</span>\n        <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">send</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">.</span>file<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token comment\">// Se o objeto req.file for undefined, ou seja, não houve sucesso, vamos imprimir um erro!</span>\n    <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">send</span><span class=\"token punctuation\">(</span><span class=\"token string\">'Houve erro no upload!'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Para <strong>testar se o armazenamento ocorreu com sucesso</strong>, apenas checamos se essa saída que o middleware <strong><em>multer.single(‘image’)</em></strong> atribuiu ao <strong><em>req.file</em></strong> não é <strong><em>undefined</em></strong>, que é seu valor se não passar no filtro, ou ocorrer algum outro erro. Ao contrário, a <strong>saída vai ser um objeto com várias propriedades, incluindo o caminho até o arquivo, por exemplo</strong>.</p>\n<p>Agora que o nosso upload está funcionando, vamos para o tratamento dessa imagem!</p>\n<h2>4. Criando nosso middleware de tratamento com Sharp</h2>\n<p>Para focar o código importante em um único lugar, <strong>vamos criar um utilitário próprio</strong>, utilizando as bibliotecas <strong><em>fs</em></strong> (já tem por default no node), e <strong>Sharp</strong>, que nós já instalamos. Para isso vou criar um arquivo chamado <em>file-helper.js</em>, que exporta uma função chamada <strong>compressImage</strong>, com toda a lógica de <strong>compressão</strong>, recebendo como parâmetro o <strong>objeto</strong> que recebemos do <strong>Multer</strong> com as <strong>informações do arquivo</strong>, <strong>e o tamanho</strong> que queremos que ela seja <strong>redimensionada</strong>.</p>\n<p>O esqueleto vai ficar assim:</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> fs <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'fs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    sharp <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'sharp'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    \nexports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">compressImage</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">file<span class=\"token punctuation\">,</span> size</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n   \n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Essa função deve <strong>redimensionar</strong> a imagem recebida, <strong>converter</strong> para <strong>WEBP</strong>, e <strong>substituir</strong> o arquivo antigo pelo tratado. No final, quero esse arquivo em <strong><em>Buffer</em></strong>, para salvar com o módulo <strong>fs</strong>.* O Sharp tem uma <a href=\"http://sharp.pixelplumbing.com/en/stable/api-constructor/\">série de possibilidades e métodos</a>, que podemos executar no <a href=\"https://schier.co/blog/2013/11/14/method-chaining-in-javascript.html\">estilo chain</a>, incluindo salvar diretamente no disco, mas nos meus testes ocorreram vários bugs, então preferi trabalhar com o <strong>Buffer em conjunto com o fs</strong>.</p>\n<p>Continuando nosso código, vamos codar a <strong>lógica de processamento</strong> até receber o Buffer, abaixo você encontra o código explicado:</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token keyword\">const</span> fs <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'fs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      sharp <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">'sharp'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    \nexports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">compressImage</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">file<span class=\"token punctuation\">,</span> size</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  \n <span class=\"token comment\">// Pegamos o PATH antigo e fazemos um tratamento com ele, para mudar a extensão do arquivo.</span>\n <span class=\"token keyword\">const</span> newPath <span class=\"token operator\">=</span> file<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">'.'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">+</span> <span class=\"token string\">'.webp'</span><span class=\"token punctuation\">;</span>\n  \n    <span class=\"token keyword\">return</span> <span class=\"token function\">sharp</span><span class=\"token punctuation\">(</span>file<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">)</span> <span class=\"token comment\">// Executamos o SHARP na imagem que queremos comprimir</span>\n  \n        <span class=\"token punctuation\">.</span><span class=\"token function\">resize</span><span class=\"token punctuation\">(</span>size<span class=\"token punctuation\">)</span> <span class=\"token comment\">//Redimensionamos para o tamanho (se não receber esse parâmetro, não redimensiona</span>\n  \n        <span class=\"token punctuation\">.</span><span class=\"token function\">toFormat</span><span class=\"token punctuation\">(</span><span class=\"token string\">'webp'</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// Forçamos a conversão esse arquivo para webp</span>\n  \n        <span class=\"token punctuation\">.</span><span class=\"token function\">webp</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> <span class=\"token comment\">// Comprimimos, setando uma qualidade</span>\n            <span class=\"token literal-property property\">quality</span><span class=\"token operator\">:</span> <span class=\"token number\">80</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  \n        <span class=\"token punctuation\">.</span><span class=\"token function\">toBuffer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// Transformamos esse arquivo em um Buffer</span>\n  \n        <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">data</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            <span class=\"token comment\">// Temos o buffer disponível para tratamento</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>   \n<span class=\"token punctuation\">}</span></code></pre></div>\n<blockquote>\n<p>Como você deve ter entendido, apenas executei a biblioteca passando o caminho da imagem que quero processar, e fui concatenando com outras funções da biblioteca, para fazer os tratamentos.</p>\n</blockquote>\n<p>Prosseguindo com o armazenamento do Buffer, nós vamos primeiro <strong>apagar o arquivo anterior salvo pelo Multer</strong>, e depois <strong>salvar o novo .webp comprimido</strong>. Se tudo ocorreu corretamente, o <strong>retorno vai ser o caminho da imagem nova</strong>, <em>para podermos usar na nossa rota, caso você tenha que salvar no banco de dados, por exemplo.</em></p>\n<blockquote>\n<p>Vou utilizar três métodos do <strong>file-system(fs)</strong>, o access, que nesse código serve para testar se o arquivo anterior existe antes de apaga-lo, o unlink, que o apagará, e o writeFile, que vai, a partir do novo caminho e do buffer, armazenar a nova imagem.</p>\n</blockquote>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\">exports<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">compressImage</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">file<span class=\"token punctuation\">,</span> size</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> newPath <span class=\"token operator\">=</span> file<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">'.'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">+</span> <span class=\"token string\">'.webp'</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token function\">sharp</span><span class=\"token punctuation\">(</span>file<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">resize</span><span class=\"token punctuation\">(</span>size<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">toFormat</span><span class=\"token punctuation\">(</span><span class=\"token string\">'webp'</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">webp</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n            <span class=\"token literal-property property\">quality</span><span class=\"token operator\">:</span> <span class=\"token number\">80</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">toBuffer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">data</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\n            <span class=\"token comment\">// Deletando o arquivo antigo</span>\n            <span class=\"token comment\">// O fs.acess serve para testar se o arquivo realmente existe, evitando bugs</span>\n            fs<span class=\"token punctuation\">.</span><span class=\"token function\">access</span><span class=\"token punctuation\">(</span>file<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">err</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\n                <span class=\"token comment\">// Um erro significa que a o arquivo não existe, então não tentamos apagar</span>\n                <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>err<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n                    \n                    <span class=\"token comment\">//Se não houve erros, tentamos apagar</span>\n                    fs<span class=\"token punctuation\">.</span><span class=\"token function\">unlink</span><span class=\"token punctuation\">(</span>file<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">,</span> <span class=\"token parameter\">err</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\n                        <span class=\"token comment\">// Não quero que erros aqui parem todo o sistema, então só vou imprimir o erro, sem throw.</span>\n                        <span class=\"token keyword\">if</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span> console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span>\n                    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n                <span class=\"token punctuation\">}</span>\n            <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            \n            <span class=\"token comment\">//Agora vamos armazenar esse buffer no novo caminho</span>\n            fs<span class=\"token punctuation\">.</span><span class=\"token function\">writeFile</span><span class=\"token punctuation\">(</span>newPath<span class=\"token punctuation\">,</span> data<span class=\"token punctuation\">,</span> <span class=\"token parameter\">err</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">if</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span><span class=\"token punctuation\">{</span>\n                    <span class=\"token comment\">// Já aqui um erro significa que o upload falhou, então é importante que o usuário saiba.</span>\n                    <span class=\"token keyword\">throw</span> err<span class=\"token punctuation\">;</span>\n                <span class=\"token punctuation\">}</span>\n            <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token comment\">// Se o código chegou até aqui, deu tudo certo, então vamos retornar o novo caminho</span>\n            <span class=\"token keyword\">return</span> newPath<span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Agora que nosso utilitário de compressão está a todo o vapor, basta <strong>executarmos na requisição do nosso POST</strong>, se o upload foi feito com **sucesso *<em>(</em>de maneira crua: se a propiedade req.file não for undefined).*</p>\n<p>Vamos então voltar até o nosso POST, importar o nosso utilitário, e executar ele!</p>\n<div class=\"gatsby-highlight\" data-language=\"javascript\"><pre class=\"language-javascript\"><code class=\"language-javascript\"><span class=\"token comment\">//Lembre-se de importar a nossa biblioteca no início do arquivo!</span>\n<span class=\"token comment\">//Ex: const filehelper = require('app/util/file-helper.js');</span>\n\napp<span class=\"token punctuation\">.</span><span class=\"token function\">post</span><span class=\"token punctuation\">(</span><span class=\"token string\">'/nova-imagem'</span><span class=\"token punctuation\">,</span> multer<span class=\"token punctuation\">.</span><span class=\"token function\">single</span><span class=\"token punctuation\">(</span><span class=\"token string\">'image'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">req<span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">,</span> next</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\n    <span class=\"token comment\">// Se houve sucesso no armazenamento</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">.</span>file<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    \n      \t<span class=\"token comment\">// Vamos mandar essa imagem para compressão antes de prosseguir</span>\n        <span class=\"token comment\">// Ela vai retornar o a promise com o novo caminho como resultado, então continuamos com o then.</span>\n         flehelper<span class=\"token punctuation\">.</span><span class=\"token function\">compressImage</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">.</span>file<span class=\"token punctuation\">,</span> <span class=\"token number\">100</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">newPath</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n                 <span class=\"token comment\">// Vamos continuar normalmente, exibindo o novo caminho</span>\n                  <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">send</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Upload e compressão realizados com sucesso! O novo caminho é:\"</span> <span class=\"token operator\">+</span>newPath <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n             <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n            <span class=\"token punctuation\">.</span><span class=\"token function\">catch</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">err</span> <span class=\"token operator\">=></span> console<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">return</span> res<span class=\"token punctuation\">.</span><span class=\"token function\">send</span><span class=\"token punctuation\">(</span><span class=\"token string\">'Houve erro no upload!'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p><strong>Tudo pronto!</strong> se tudo ocorreu corretamente, temos a nossa imagem recebida pelo post <strong>“substituída” pela nova versão comprimida e processada.</strong></p>\n<blockquote>\n<p>Tem várias coisas que podem ser melhoradas no código, como por exemplo salvar o buffer por meio de stream, continuar a promise chain para deixar tudo async, trabalhar melhor com os erros, etc. Vou deixar isso para a individualidade do projeto de vocês,** o céu é o limite**!</p>\n</blockquote>\n<p>Se você chegou até aqui,** parabéns guerreiro<strong>! Espero que esse **artigo</strong> tenha o ajudado solucionar pelo menos um pouco dos seus problemas <strong>com upload</strong> e <strong>processamento</strong> de imagens com <strong>nodeJS</strong>. E <strong>se ajudou mesmo</strong>, não esquece de <strong>deixar um aplauso</strong>, clicado na mãozinha aí em cima, e deixando um <strong>feedback nos comentários</strong>!</p>\n<p>Se o código acima <strong>não deu certo</strong> no seu projeto, ocorreu algum <strong>bug</strong> ou você <strong>ficou alguma dúvida</strong>, vou ficar muito feliz em tentar <strong>ajudar</strong> nas minhas <strong>redes sociais</strong>! Chama no probleminha:</p>\n<ul>\n<li><a href=\"https://www.facebook.com/vitor.regisderamos\">Facebook</a></li>\n<li><a href=\"https://www.linkedin.com/in/vitorregisr/\">Linkedin</a></li>\n<li><a href=\"https://twitter.com/vitorregisrr\">Twitter</a></li>\n<li><a href=\"https://github.com/vitorregisrr\">Github</a></li>\n</ul>","timeToRead":12,"fields":{"slug":"/blog/upload-e-compressao-de-imagens-com-nodejs/"},"frontmatter":{"title":"Upload e compressão de imagens com NodeJS","category":"node","image":{"publicURL":"/static/65eeecb3ca8154fd9dfc0dc6cd64f55f/node-compressao.png"},"description":"Como fazer o upload de imagens, e principalmente: como comprimi-las e tratá-las após recebe-las no servidor.","date":"30 de junho de 2019"}}},"pageContext":{"slug":"/blog/upload-e-compressao-de-imagens-com-nodejs/"}},"staticQueryHashes":["1342666553","2249152690","3023333919","3737124155","4076689451","515424126","698804963","94195747"]}