Skip to main content

Como lidar com uploads de arquivos no Node.js com Fastify

Escrito por

Amarachi Aso

feature fastify js upload

8 de dezembro de 2022

0 minutos de leitura

O upload de arquivos é uma funcionalidade bastante comum em aplicações web. Por isso, muitos frameworks web oferecem maneiras de permitir que aplicações no servidor recebam arquivos e trabalhem com eles. Isso também vale para o Fastify, um framework web para Node.js frequentemente considerado sucessor do Express. Ele é rápido e tem como foco oferecer a melhor experiência para desenvolvedores, com uma arquitetura de plugins poderosa e o mínimo de sobrecarga possível.

Neste tutorial, você vai usar o framework Fastify em uma aplicação Node.js para configurar o upload de arquivos. Você vai aprender como os uploads funcionam em um servidor comum e como lidar com eles em uma aplicação de servidor Fastify.

Noções básicas sobre upload de arquivos

Para fazer upload de arquivos para um servidor de aplicações, o computador cliente precisa enviar uma requisição POST ao servidor. No corpo da requisição, ele envia dados normalmente enviados por formulários HTML. Os formulários HTML oferecem três métodos de codificação de dados:

  • application/x-www-form-urlencoded (padrão) é o tipo de conteúdo ou método de codificação padrão para formulários HTML. No entanto, quando os formulários incluem arquivos, é preciso usar o método multipart/form-data.

  • multipart/form-data representa as várias partes ou campos de um formulário HTML, enviando as partes como pares nome/valor no corpo da requisição. O nome e o valor de cada par correspondem, respectivamente, ao nome e ao valor dos campos de entrada.

  • text/plain é usado para enviar dados como texto simples, sem formatação, e não pode ser usado para enviar arquivos.

Quando arquivos são enviados a um servidor de aplicações, geralmente é para que o servidor os processe, salve ou faça as duas coisas. As aplicações no servidor podem processar e salvar arquivos enviados de várias maneiras, incluindo:

  • Salvá-los no sistema de arquivos do próprio servidor. Isso pode ser feito em um diretório dedicado no mesmo servidor em que a aplicação está em execução ou em outro servidor dedicado ao armazenamento de arquivos.

  • Armazená-los em um banco de dados.

  • Enviar os arquivos para um serviço de armazenamento em nuvem, como Amazon S3 ou Cloudinary.

Este tutorial se concentra no uso do primeiro método para lidar com uploads de arquivos.

Como implementar o upload de arquivos com Fastify

Antes de começar este tutorial, você vai precisar de:

  • Node.js instalado

  • Um editor de código de sua preferência, como o Visual Studio Code (VS Code)

  • Postman: você pode fazer login e usar o aplicativo web ou instalá-lo no computador.

Nas próximas seções, você vai criar um projeto Fastify, configurar um plugin de upload de arquivos, criar uma rota /upload capaz de lidar com uploads de vários arquivos e escrever um caso de teste para essa rota.

Crie um projeto e configure o Fastify

Para começar, crie um projeto e configure o Fastify. Acesse o diretório em que deseja criar o projeto e crie uma pasta chamada fastify-file-upload. No terminal, execute o seguinte comando:

mkdir fastify-file-upload

Acesse essa pasta:

cd fastify-file-upload

Em seguida, abra a pasta no editor de código de sua preferência. Se estiver usando o VS Code, você pode executar o seguinte comando:

code .

Inicialize o projeto e crie um arquivo package.json executando:

npm init -y

Em seguida, instale as dependências que você vai usar neste projeto:

npm install fastify @fastify/multipart form-auto-content tap

E crie dois arquivos: app.js e server.js:

touch app.js server.js

O arquivo app.js vai conter o código da aplicação. Copie e cole nele o seguinte código:

import Fastify from 'fastify'

export const app = Fastify({
    logger: true
  })

import Fastify from fastify

Esse código importa o framework Fastify e cria uma instância de aplicação com ele. Em seguida, adicione o código abaixo ao arquivo server.js:

import { app } from './app.js'

const start = async () => {
    try {
      await app.listen({ port: 8000 })
      console.log(`server listening on ${app.server.address().port}`)
    } catch (err) {
        app.log.error(err)
      process.exit(1)
    }
  }
  start()

Esse código inicia a aplicação quando ela é executada ou retorna um erro caso ocorra algum problema ao iniciá-la.

Neste projeto, você vai usar a sintaxe de módulos ES6. Portanto, seus arquivos JavaScript precisam ser carregados como módulos. Para isso, adicione a propriedade type ao arquivo package.json e defina o valor como module:

"type": "module"

Configure um plugin de upload de arquivos

O Fastify-multipart oferece suporte a tipos de conteúdo multipart no Fastify. Como você já instalou o plugin junto com as dependências, agora é hora de configurá-lo. Para isso, importe multipart de '@fastify/multipart' e registre-o.

Adicione ao arquivo app.js as linhas que começam com +:

  import Fastify from 'fastify'
+ import multipart from '@fastify/multipart'

export const app = Fastify({
    logger: true
  })

+ app.register(multipart)

Aqui, você registrou o multipart na aplicação Fastify criada anteriormente. Agora, a instância da aplicação pode analisar facilmente tipos de conteúdo multipart.

Uploads de arquivos

Há várias maneiras de acessar os arquivos de uma requisição POST. Por exemplo, se você sabe que uma requisição contém apenas um arquivo, pode acessá-lo com o seguinte código:

const data = await request.file()

Neste código, request é o parâmetro da requisição recebido pela função de tratamento da rota de upload, cuja função de tratamento se parece com esta:

…
app.post('/upload', async function (request, reply) {

  const data = await request.file()

  // The rest of your code
}

Se você espera vários arquivos, basta chamar o método files() em vez de file(), assim:

const data = await request.files()

No arquivo app.js, você precisa escrever o código para implementar completamente o upload de vários arquivos. Mas, primeiro, crie um diretório para armazenar os arquivos enviados à aplicação. Crie esse diretório na raiz do projeto e dê a ele o nome uploads:

mkdir uploads

Depois, faça as seguintes alterações no arquivo app.js:

import Fastify from 'fastify'
import multipart from '@fastify/multipart'
+ import fs from 'fs'
+ import util from 'util'
+ import { pipeline } from 'stream'

+ const pump = util.promisify(pipeline)

export const app = Fastify({
    logger: true
  })

  app.register(multipart)

+   app.post('/upload', async function (req, reply) {

+ const parts = req.files()
+ 
+ for await (const part of parts) {

+     // upload and save the file
+     await pump(part.file, fs.createWriteStream(`./uploads/${part.filename}`))

+ }

+     return {message : 'files uploaded' }
+ })

No código atualizado, você importou funções dos módulos principais do Node.js fs, util e stream. Em seguida, criou uma rota /upload e sua função de tratamento.

Dentro da função de tratamento, você usou req.files() para acessar todos os arquivos da requisição. Você percorreu os arquivos e, para cada um, o código await pump(part.file, fs.createWriteStream(`./uploads/${part.filename}`)) faz o upload e o salva no diretório de uploads criado anteriormente.

Observe que os arquivos enviados não ficam acumulados na memória antes de serem salvos. Em vez disso, a função fs.createWriteStream cria um fluxo no destino, enquanto o fluxo de arquivo proveniente de part.file é direcionado para o novo fluxo. Assim, o conteúdo do arquivo não é armazenado primeiro na memória; os dados são gravados diretamente no arquivo em blocos, à medida que passam pelo fluxo. Isso é especialmente útil para enviar arquivos grandes. Esse método não bloqueia o loop de eventos e também é eficiente no uso de memória para lidar com arquivos na aplicação.

Agora é hora de iniciar o servidor e enviar arquivos para ele usando o Postman. No arquivo package.json, substitua o script de inicialização pelo seguinte:

"start": "node server.js"

Em seguida, inicie o servidor com:

npm start

Acesse o Postman e envie uma requisição POST para http://localhost:8000/upload. No corpo da requisição, selecione a opção form-data e envie dois arquivos de sua escolha:

Interface do Postman mostrando uma solicitação POST para /upload com campos de form-data para um arquivo de texto de artigo e uma imagem de capa.
Interface do Postman para fazer upload de arquivos.

Neste exemplo, você envia um arquivo chamado article.txt e outro chamado cover.jpg (as respectivas chaves serão "article" e "coverImage"). Depois de enviar os arquivos, você deverá receber o código de status 200 e o objeto de retorno {message : 'files uploaded' }:

Solicitação de upload via form-data no Postman exibindo os arquivos article.txt e cover.jpg, com resposta 200 OK confirmando o envio dos arquivos.
Dois arquivos enviados pelo Postman.

Você também deverá ver os arquivos adicionados ao diretório "upload" criado anteriormente para armazenar os arquivos enviados.

Neste ponto, você já sabe lidar com uploads de vários arquivos usando o plugin fastify/multipart. A maioria dos formulários exige o envio não apenas de arquivos, mas também de arquivos e outros campos de texto. Para lidar com formulários desse tipo, modifique o código existente para processar tanto os fluxos de arquivos quanto os outros campos.

Para isso, faça as seguintes alterações no arquivo app.js:

import Fastify from 'fastify'
import multipart from '@fastify/multipart'
import fs from 'fs'
import util from 'util'
import { pipeline } from 'stream'

const pump = util.promisify(pipeline)

export const app = Fastify({
    logger: true
  })

  app.register(multipart)

  app.post('/upload', async function (req, reply) {

+ const parts = req.parts()

for await (const part of parts) {

+  if (part.file) {

    // upload and save the file
    await pump(part.file, fs.createWriteStream(`./uploads/${part.filename}`))

+  } else {
+    // do something with the non-files parts
+  }

}

    return {message : 'files uploaded' }
})

Observe que, desta vez, você usa req.parts() para acessar tanto os fluxos de arquivos quanto os campos, em vez de req.files(). No corpo do loop, você usa uma instrução if para identificar quais partes são arquivos e tratá-las de maneira diferente dos campos.

Agora é hora de fazer outra requisição. Salve o arquivo, pare e reinicie o servidor com npm start. Volte ao Postman, exclua os pares de chave/valor da requisição anterior e crie novos. No primeiro campo, informe seu nome completo e, em seguida, adicione dois arquivos quaisquer, como antes.

Observação: Se você pretende usar os mesmos arquivos, talvez queira excluir do diretório de uploads aqueles que enviou anteriormente.

Sua requisição deverá ficar assim:

Requisição form-data no Postman mostrando um campo e dois arquivos enviados, article.txt e cover.jpg, com resposta 200 OK.
Um campo e dois arquivos enviados pelo Postman.

Se estiver tudo certo, sua aplicação agora poderá lidar com vários fluxos de arquivos e campos.

Escreva casos de teste

Para proteger sua implementação de upload de arquivos, você precisa escrever testes. Mas como testar uploads de arquivos no Fastify? Neste exemplo, você vai usar o framework de testes tap, o pacote form-auto-content e o método .inject do Fastify para escrever os casos de teste.

Crie o arquivo app.test.js na raiz do projeto. Em seguida, copie e cole nele o seguinte código:

'use strict'

import { test } from 'tap'
import formAutoContent from 'form-auto-content'
import fs from 'fs'
import { app } from './app.js'

const myForm  = formAutoContent({
    fullName: 'Amarachi Aso',
    article: fs.createReadStream(`./sample_files/file.txt`),
    coverImage: fs.createReadStream(`./sample_files/cover.jpg`)
  })

test('submits form with multiple files in it', async t => {

    const res = await app.inject({
        method: 'post',
        url: '/upload',
        ...myForm
      })
  t.equal(res.statusCode, 200, 'returns a status code of 200')
})

Substitua os caminhos ./sample_files/file.txt e ./sample_files/cover.jpg pelos caminhos de dois arquivos quaisquer do seu sistema. Ou você pode copiar estes arquivos aqui.

O método .inject do Fastify permite injetar requisições HTTP simuladas na aplicação sem executar o código em um servidor real. No código anterior, observe como você usou formAutoContent para criar um formulário chamado myForm e depois usou esse formulário como corpo da requisição POST simulada dentro do caso de teste. Há apenas um caso de teste, que espera que, ao enviar um formulário com vários arquivos, a aplicação retorne um statusCode igual a 200.

Execute o teste e confirme que ele foi aprovado. No arquivo package.json, substitua o script de teste por este:

"test": "tap --reporter=list --watch",

Em seguida, execute:

npm test

O teste deverá passar e a saída deverá ficar assim:

Terminal mostrando um teste de upload de arquivo com Fastify aprovado, com código de status 200 e resumo da cobertura
Teste aprovado.

Todo o código deste tutorial está disponível neste repositório no GitHub.

Conclusão

Neste artigo, você aprendeu o básico sobre upload de arquivos e por que formulários com arquivos só são codificados usando o multipart/form-data content-type. Você também aprendeu que aplicações no servidor podem processar e salvar arquivos de duas maneiras: em um diretório dedicado no mesmo servidor ou em um serviço de armazenamento em nuvem, como Amazon S3. Por fim, aprendeu a lidar com uploads no Fastify, um framework web rápido e com pouca sobrecarga para Node.js.

Se quiser saber mais, consulte a documentação do plugin Fastify-multipart e o guia do Fastify sobre testes.

Publicado em:

Leia mais

Blog

Verão nos estádios: a turnê Fan Zone do Snyk Connect

A turnê Fan Zone da Snyk levou workshops de segurança de IA, networking e competição amistosa a 8 cidades e 3 sessões virtuais. Os participantes desenvolveram habilidades, trocaram ideias e evoluíram juntos.

Blog

Snyk VulnBench JS 1.0: LLMs conseguem encontrar os mesmos bugs duas vezes?

Snyk VulnBench JS 1.0: 300 varreduras repetidas mostram que as descobertas de segurança dos LLMs variam a cada execução, enquanto SAST e modelos detectam diferentes lacunas de vulnerabilidade.

feature insights context
Blog

Construindo a segurança de IA com nossos clientes: 5 lições do programa de parceiros de design do Evo

Conheça 5 lições importantes do programa de parceiros de design do Evo, da Snyk. Descubra como a descoberta de IA, a inteligência de risco e a automação de políticas ajudam as equipes a proteger a IA generativa e controlar a proliferação de IA em grande escala.