Subiendo archivos a AWS-S3 con Nodejs/ExpressJS.

PutObjectCommand vs Upload o Buffer vs Readable?

Si vas empezando en el mundo del desarrollo de sofware o llevas muchooo tiempo en esto como algunos como yo(ya mi esplada es testigo de esto) tendrás que lidear con archivos, subirlos a un servidor, validar que sean correctos y muuuchas otras funcionalidades.

Usaremos ExpressJS para nuestro backend y subiremos nuestros archivos a AWS-S3 mediante el SDK para Javascript que nos proporciona AWS.

El primer paso que debemos saber son las características de los archivos que vamos a subir. Serán archivos que pesan GB? queremos enviar nuestro archivo a S3 en pedacitos o todo de un tiro? Esto nos servirá para saber que estructura de datos vamos a usar, ya sea Buffer o Readable.

Características de Buffer.

  • Tipo de dato que nos permite trabajar con una cantidad fija de memoria en NodeJS.

  • Es usado para representar datos binarios (secuencia de bytes), es muy utilizado para manejar datos que provienen de archivos, la red o alguna fuente de stream.

  • La implementación o clase Buffer nos permite hacer muchas cosas con los datos, convertirlo a string, copiarlos y demás operaciones que pueden ver en la documentación oficial de NodeJS.

Casos de uso para Buffer:

  • Lectura o escritura de archivos.

  • Manejo de datos binarios, como imágenes, archivos de video u otros medios.

  • Comunicación en red (por ejemplo, trabajar con datos TCP/UDP)

const buffer = Buffer.from('Hello, world!', 'utf-8');
console.log(buffer); // <Buffer 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21>

Características de Readable Stream.

Buffer y Readable son muy parecidos en cuanto a su uso. Podemos usarlos para leer y escribir archivos, manejar paquetes de red y la gran diferencia yace en que Readable el manejo de datos se hace por pedazos y en Buffer es en un solo conjunto.

Características:

  • Datos transmitidos: los flujos legibles manejan los datos en fragmentos, lo que le permite procesarlos a medida que llegan en lugar de esperar a que se carguen todos los datos. Esto los hace más eficientes en términos de memoria para conjuntos de datos grandes.

  • Datos a on-demand: los datos se pueden leer de un flujo legible ya sea manualmente llamando al método .read() o automáticamente escuchando el evento 'data'.

  • Asíncrono: los flujos legibles son asíncronos y son adecuados para manejar grandes cantidades de datos o datos que llegan con el tiempo (por ejemplo, leer un archivo grande o recibir datos a través de la red).

Casos de uso:

Leer archivos grandes (por ejemplo, video, audio) o grandes cantidades de datos que no caben en la memoria a la vez. Manejo de solicitudes y respuestas de red. Transmisión de datos (por ejemplo, video en vivo o transmisión de audio).

const fs = require('fs');

const readableStream = fs.createReadStream('archivoGrande.txt', { encoding: 'utf8' });

readableStream.on('data', (chunk) => {
  console.log(`Procesando ${chunk.length} bytes of data.`);
});

readableStream.on('end', () => {
  console.log('Terminamos de leer datos.');
});

Una vez que elijamos el tipo de datos que vamos a usar, pasamos a la implementación de AWS, que dependerá si usaremos stream de datos o subiremos todo de un solo golpe.

PutObjectCommand de la librería @aws-sdk/client-s3

Este enfoque es una forma sencilla y directa de cargar un archivo en S3. PutObjectCommand es parte del cliente básico de S3, que nos permite realizar operaciones básicas en S3 como cargar archivos, enumerar contenedores/buckets, eliminar objetos, etc. Es sencillo y funciona bien para archivos de tamaño pequeño a moderado, por eso usar Buffer con PutObjectCommand puede ser una eficiente combinación si no planeas crear el nuevo Netflix o DropBox.

Casos de uso:

  • Cuando necesita subir archivos pequeños donde no es necesaria la carga de varias partes o stream.

  • Si no necesita funciones avanzadas como el seguimiento del progreso de la subida, el manejo de reintentos o la personalización de la concurrencia.

const data = await this.client.send(new PutObjectCommand(params));

Upload de la librería @aws-sdk/lib-storage

  • Esta opción utiliza el paquete @aws-sdk/lib-storage, que proporciona herramientas para manejar archivos más avanzadas, en particular para archivos grandes.

  • Está diseñada para manejar archivos en varias partes de forma automática.

  • El poder subir datos en partes es un método que nos permite procesar archivos grandes de forma mas eficiente, es útil para el rendimiento y la confiabilidad, especialmente para archivos de más de 5 GB.

  • Ofrece características adicionales como seguimiento del progreso, control de concurrencia (queueSize) y la capacidad de dejar partes con errores para su manejo manual.

Casos de uso:

  • Subir archivos grandes por partes.

  • Manejar el reintento, el progreso y errores de la subida de archivos.

const upload = new Upload({
  client: new S3Client({}),
  tags: [...], 
  queueSize: 4, 
  leavePartsOnError: false, 
  params: target,
});
await upload.done();

Ejemplo final

import express, { Request, Response } from 'express';
import multer from 'multer';

const app = express();

// Configurar Multer para guardar archivos en memoria
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

// Endpoint q sube el archivo
app.post('/upload', upload.single('file'), async (req: Request, res: Response) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded.');
  }

  //El archivo subido es accessible desde req.file
  const file = req.file;

  // Ejem para subir a s3
  const params = {
    Bucket: 'your-bucket-name',
    Key: file.originalname, //Nombre que se guardara en S3
    Body: file.buffer,      // Contenido .. buffer puede ser un Readable stream 
    ContentType: file.mimetype,
  };

  try {
    const result = await s3Client.send(new PutObjectCommand(params));
    res.status(200).send(`Archivo subido correctamente. S3 URL: ${result.Location}`);
  } catch (error) {
    res.status(500).send(`Fallo el proceso. Error: ${error.message}`);
  }
});

app.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});

Espero les sirva y no duden en dejar sus comentarios o suscribirse al newsletter.