Skip to main content

Cómo manejar la carga de archivos en Node.js con Fastify

Escrito por

Amarachi Aso

feature fastify js upload

8 de diciembre de 2022

0 minutos de lectura

La carga de archivos es una funcionalidad común en las aplicaciones web. Por eso, muchos frameworks web ofrecen formas para que las aplicaciones del lado del servidor acepten archivos y trabajen con ellos. Este es el caso de Fastify, un framework web para Node.js que suele considerarse el sucesor de Express. Es rápido y se enfoca en ofrecer la mejor experiencia para desarrolladores, con una potente arquitectura de plugins y la menor sobrecarga posible.

En este tutorial, usarás el framework Fastify en una aplicación Node.js para configurar la carga de archivos. Aprenderás cómo funcionan las cargas de archivos en un servidor típico y cómo manejarlas en una aplicación de servidor Fastify.

Conceptos básicos de la carga de archivos

Si quieres cargar archivos en un servidor de aplicaciones, una computadora cliente debe enviar una solicitud POST al servidor. En el cuerpo de la solicitud, enviará datos que normalmente se envían mediante formularios HTML. Los formularios HTML ofrecen tres métodos para codificar datos:

  • application/x-www-form-urlencoded (predeterminado) es el tipo de contenido o la codificación predeterminados de los formularios HTML. Sin embargo, cuando se envían archivos mediante este formulario, se deben codificar con el método multipart/form-data.

  • multipart/form-data se usa para representar las distintas partes o campos de un formulario HTML. Las partes se envían como pares de nombre y valor en el cuerpo de la solicitud. El nombre y el valor de cada par corresponden, respectivamente, al nombre y al valor de los campos de entrada.

  • text/plain se usa para enviar datos en texto sin formato y no permite enviar archivos.

Cuando se envían archivos a un servidor de aplicaciones, el objetivo suele ser que el servidor los procese, los guarde o haga ambas cosas. Las aplicaciones de servidor pueden procesar y guardar los archivos cargados de varias maneras, entre ellas:

  • Guardarlos en el sistema de archivos de tu propio servidor. Puede ser en un directorio dedicado del mismo servidor donde se ejecuta tu aplicación o en otro servidor dedicado al almacenamiento de archivos.

  • Guardarlos en una base de datos.

  • Cargar los archivos en un servicio de almacenamiento en la nube, como Amazon S3 o Cloudinary.

Este tutorial se centra en el primer método para manejar las cargas de archivos.

Cómo implementar la carga de archivos con Fastify

Antes de comenzar este tutorial, necesitarás lo siguiente:

  • Node.js instalado

  • Un editor de código de tu elección, como Visual Studio Code (VS Code)

  • Postman. Puedes iniciar sesión y usar la aplicación web o instalarla en tu computadora.

En las siguientes secciones, crearás un proyecto Fastify, configurarás un plugin para cargar archivos, crearás una ruta /upload que pueda manejar varias cargas de archivos y escribirás un caso de prueba para la ruta.

Crea un proyecto y configura Fastify

Para comenzar, debes crear un proyecto y configurar Fastify. Ve al directorio donde quieras crear el proyecto y crea una carpeta llamada fastify-file-upload. En la terminal, ejecuta este comando:

mkdir fastify-file-upload

Cambia el directorio actual a esta carpeta:

cd fastify-file-upload

Luego, abre la carpeta en tu editor de código preferido. Si usas VS Code, puedes ejecutar este comando:

code .

Inicializa el proyecto y crea un archivo package.json ejecutando lo siguiente:

npm init -y

Luego, instala las dependencias que usarás en este proyecto:

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

Y crea dos archivos: app.js y server.js:

touch app.js server.js

app.js contendrá el código de tu aplicación. Copia y pega en él el siguiente código:

import Fastify from 'fastify'

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

import Fastify from fastify

Este código importa el framework Fastify y crea una instancia de una aplicación a partir de él. Luego, agrega el siguiente código al archivo 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()

Este código ejecutará tu aplicación cuando la inicies o devolverá un error si ocurre algún problema al hacerlo.

En este proyecto, usarás la sintaxis de módulos ES6; por lo tanto, tus archivos JavaScript deben cargarse como módulos. Para asegurarte de que se carguen como módulos, agrega la propiedad type a tu archivo package.json y asigna el valor module:

"type": "module"

Configura un plugin para cargar archivos

Fastify-multipart ofrece compatibilidad con tipos de contenido multipart en Fastify. Como ya instalaste el plugin junto con las dependencias, es hora de configurarlo. Para ello, importa multipart desde '@fastify/multipart' y luego regístralo.

Agrega las líneas que comienzan con + al contenido de tu archivo app.js:

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

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

+ app.register(multipart)

Aquí registraste multipart en la aplicación Fastify que creaste antes. Ahora, la instancia de tu aplicación puede analizar fácilmente el tipo de contenido multipart.

Carga de archivos

Hay varias formas de acceder a los archivos de una solicitud POST. Por ejemplo, si sabes que una solicitud contiene un solo archivo, puedes acceder a él con el siguiente código:

const data = await request.file()

En este código, request es el parámetro de solicitud de la función controladora de tu ruta de carga. La función controladora se vería más o menos así:

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

  const data = await request.file()

  // The rest of your code
}

Si esperas varios archivos, la diferencia es que llamarías al método files() en lugar de file(), así:

const data = await request.files()

En tu archivo app.js, debes escribir código para implementar por completo la carga de varios archivos. Pero primero, crea un directorio donde se guardarán los archivos cargados en tu aplicación. Crea este directorio en la raíz del proyecto y llámalo uploads:

mkdir uploads

Después, haz las siguientes modificaciones en el archivo 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' }
+ })

En el código modificado, importaste funciones de los módulos principales de Node.js fs, util y stream. Luego, creaste una ruta /upload y su función controladora.

Dentro de la función controladora, usaste req.files() para acceder a todos los archivos de la solicitud. Recorriste los archivos y, para cada uno, el código await pump(part.file, fs.createWriteStream(`./uploads/${part.filename}`)) lo carga y lo guarda en el directorio de cargas que creaste antes.

Ten en cuenta que los archivos que cargas no se acumulan en la memoria antes de guardarse. En su lugar, se usa la función fs.createWriteStream para crear un flujo en el extremo receptor, mientras que el flujo de archivo que proviene de part.file se envía al flujo recién creado. Esto garantiza que el contenido del archivo no se almacene primero en un búfer en la memoria; en cambio, los fragmentos de datos se escriben directamente en el archivo a medida que llegan por el flujo. Esto es especialmente útil para cargar archivos grandes. No bloquea el bucle de eventos y, además, permite manejar archivos en tu aplicación con un uso eficiente de la memoria.

Ahora es momento de iniciar el servidor y enviarle archivos con Postman. En tu archivo package.json, reemplaza el script de inicio por el siguiente:

"start": "node server.js"

Luego, inicia el servidor con este comando:

npm start

Ve a Postman y envía una solicitud POST a http://localhost:8000/upload. En el cuerpo de la solicitud que vas a enviar, selecciona la opción form-data y carga dos archivos de tu elección:

Interfaz de Postman que muestra una solicitud POST a /upload con campos de form-data para un archivo de texto de artículo y una imagen de portada.
Interfaz de Postman para cargar archivos.

Aquí, cargas un archivo llamado article.txt y otro llamado cover.jpg (sus claves serán «article» y «coverImage», respectivamente). Después de cargar los archivos, deberías obtener el código de estado 200 y el objeto de respuesta {message : 'files uploaded' }:

Solicitud de carga con form-data en Postman que muestra los archivos article.txt y cover.jpg, con una respuesta 200 OK que confirma que se cargaron los archivos.
Se subieron dos archivos desde Postman.

También deberías ver los archivos agregados al directorio «upload» que creaste antes para las cargas.

En este punto, ya manejaste correctamente la carga de varios archivos con el plugin fastify/multipart. La mayoría de los formularios requieren que proporciones no solo archivos, sino también otros campos de texto. Para manejar este tipo de formularios, debes modificar el código existente para procesar tanto los flujos de archivos como los demás campos.

Para hacerlo, modifica tu archivo app.js de la siguiente manera:

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' }
})

Observa que esta vez usas req.parts() para acceder tanto a los flujos de archivos como a los campos, en lugar de req.files(). Además, en el cuerpo del bucle, usas una instrucción if para determinar qué parte es un archivo y manejarla de forma distinta a los campos.

Ahora es momento de enviar otra solicitud. Guarda el archivo y luego detén y reinicia el servidor con npm start. Vuelve a Postman, elimina los pares clave/valor de la solicitud anterior y crea otros nuevos. En el primer campo, escribe tu nombre completo y luego agrega dos archivos cualesquiera, como antes.

Nota: Si vas a usar los mismos archivos, quizá quieras eliminar los que cargaste la última vez del directorio de cargas.

Tu solicitud debería verse así:

Solicitud form-data de Postman que muestra un campo y dos archivos cargados, article.txt y cover.jpg, con una respuesta 200 OK.
Un campo y dos archivos cargados desde Postman.

Si es así, tu aplicación ya puede manejar varios flujos de archivos y campos.

Escribe casos de prueba

Para proteger la implementación de carga de archivos, debes escribir pruebas. Pero ¿cómo puedes escribir casos de prueba para la carga de archivos en Fastify? En este caso, usarás el framework de pruebas tap, el paquete form-auto-content y el método .inject de Fastify para escribir los casos de prueba.

Crea el archivo app.test.js en la raíz del proyecto. Luego, copia y pega en él el siguiente 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')
})

Reemplaza las rutas ./sample_files/file.txt y ./sample_files/cover.jpg por las rutas de dos archivos cualesquiera de tu sistema de archivos. También puedes obtener una copia de estos archivos aquí.

El método .inject de Fastify te permite inyectar solicitudes HTTP ficticias en tu aplicación sin ejecutar el código en un servidor real. En el código anterior, observa cómo usaste formAutoContent para crear un formulario llamado myForm y luego lo usaste como cuerpo de la solicitud en una solicitud POST ficticia dentro del caso de prueba. Solo tienes un caso de prueba, que espera que, al enviar un formulario con varios archivos, la aplicación devuelva un statusCode de 200.

Ejecuta esta prueba y confirma que pase. En tu archivo package.json, reemplaza el script de prueba por este:

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

Luego, ejecuta lo siguiente:

npm test

La prueba debería pasar y el resultado debería verse así:

Terminal que muestra una prueba de carga de archivos con Fastify exitosa, con código de estado 200 y resumen de cobertura
Prueba superada.

Todo el código de este tutorial está disponible en este repositorio de GitHub.

Conclusión

En este artículo, aprendiste los conceptos básicos de la carga de archivos y por qué los formularios que contienen archivos solo se codifican con el multipart/form-data content-type. También aprendiste las dos formas en que las aplicaciones de servidor pueden procesar y guardar archivos: en un directorio dedicado del mismo servidor o en un servicio de almacenamiento en la nube, como Amazon S3. Después, aprendiste a manejar cargas en Fastify, un framework web rápido y ligero para Node.js.

Si quieres aprender más, consulta la documentación del plugin Fastify-multipart y la guía de Fastify sobre pruebas.

Publicado en:

Leer más

Blog

Verano en los estadios: la gira Snyk Connect Fan Zone

La gira Fan Zone de Snyk llevó talleres de seguridad de IA, networking y competencia amistosa a 8 ciudades y 3 sesiones virtuales. Los asistentes desarrollaron habilidades, compartieron ideas y crecieron juntos.

Blog

Snyk VulnBench JS 1.0: ¿Pueden los LLM encontrar los mismos errores dos veces?

Snyk VulnBench JS 1.0: 300 análisis repetidos muestran que los hallazgos de seguridad de los LLM varían en cada ejecución, mientras que SAST y los modelos detectan distintas vulnerabilidades.

feature insights context
Blog

Cómo construir seguridad de IA junto con nuestros clientes: 5 lecciones del programa de socios de diseño de Evo

Conoce 5 lecciones clave del programa de socios de diseño de Evo de Snyk. Descubre cómo el descubrimiento de IA, la inteligencia de riesgos y la automatización de políticas ayudan a los equipos a proteger la IA generativa y controlar la proliferación de IA a gran escala.