Manejo de cargas de archivos en Node.js con Express y Multer

La mayoría de los sitios web interactivos de hoy admiten la carga de archivos. En este artículo, usaremos Multer, Formidable y Multiparty para facilitar las capacidades de carga de archivos en Node.js.

Introducción

Los usuarios no solo consumen datos, también producen datos y los suben. Pueden enviar datos a través de aplicaciones como mensajeros o correos electrónicos a destinatarios específicos o subir archivos a redes sociales y plataformas de transmisión de datos como Facebook o YouTube.

Dicho esto, casi todos los sitios web interactivos de hoy admiten la carga de archivos.

Bibliotecas de carga de archivos

Hay varias bibliotecas de nodos disponibles en NPM que pueden simplificar el proceso de validación y carga de archivos al servidor. Entre ellos, las opciones más populares en estos días son Multar, Formidable y multipartidario.

Todos tienen versiones estables y están respaldados por una comunidad en línea de desarrolladores de código abierto.

¿Qué es Multer?

Multer es un popular middleware de Node.js que se usa para manejar solicitudes multipart/form-data. Hace uso de ayudante de mesero para analizar los datos que recibe a través de un formulario HTML. Esto mejora en gran medida su rendimiento porque el módulo ayudante de camarero no tiene comparación cuando se trata de analizar datos de formularios.

Multer nos brinda control y flexibilidad al manejar solicitudes multipart/form-data: obtenemos información detallada sobre cada archivo cargado, la capacidad de agregar un motor de almacenamiento personalizado, validación de archivos de acuerdo con nuestras necesidades, la capacidad de establecer límites en la carga archivos, etc

Configuración del proyecto

Dado que no almacenaremos nuestras imágenes en una base de datos, sino en una carpeta simple por brevedad y simplicidad, hagamos otra carpeta dentro de nuestra carpeta de proyecto y asígnele el nombre, digamos, cargas.

Ahora, instalemos Express:

1
$ npm i express

Y finalmente, instalemos Multer:

1
$ npm i multer

Implementación del proyecto {#implementación del proyecto}

En este punto, estamos listos para escribir algo de código, comenzando con los formularios HTML que usaremos para recopilar información.

Comencemos con el formulario para cargar un solo archivo:

1
2
3
4
5
6
7
8
9
<form method="POST" action="/upload-profile-pic" enctype="multipart/form-data">
    <div>
        <label>Select your profile picture:</label>
        <input type="file" name="profile_pic" />
    </div>
    <div>
        <input type="submit" name="btn_upload_profile_pic" value="Upload" />
    </div>
</form>

Y luego con un formulario que nos permite subir varios archivos:

1
2
3
4
5
6
7
8
9
<form method="POST" action="/upload-multiple-images" enctype="multipart/form-data">
    <div>
        <label>Select multiple images:</label>
        <input type="file" name="multiple_images" multiple />
    </div>
    <div>
        <input type="submit" name="btn_upload_multiple_images" value="Upload" />
    </div>
</form>

Puede colocar estos formularios en páginas separadas o en la misma. A los efectos de este tutorial, son uno tras otro:

HTML5 forms for submitting files to server

Los formularios HTML son bastante sencillos, aceptan multipart/form-data y se enrutan a las funciones adecuadas que manejan sus solicitudes.

Aplicación Express

Con nuestros formularios listos, podemos trabajar en la lógica real para cargar y validar archivos a través de Express.

Vamos a crear un archivo llamado app.js en la raíz del proyecto y comencemos importando los módulos necesarios:

1
2
3
const express = require('express');
const multer = require('multer');
const path = require('path');

Ahora, creemos nuestra aplicación Express:

1
const app = express();

Y finalmente, configuremos el puerto en el que se ejecutará:

1
const port = process.env.PORT || 3000;

El directorio public fuera de nuestra carpeta raíz contiene los archivos estáticos que queremos servir, así que configurémoslo como un directorio estático usando express.static:

1
app.use(express.static(__dirname + '/public'));

En este punto, definamos la ubicación de almacenamiento para nuestras imágenes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const storage = multer.diskStorage({
    destination: function(req, file, cb) {
        cb(null, 'uploads/');
    },

    // By default, multer removes file extensions so let's add them back
    filename: function(req, file, cb) {
        cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
    }
});

Y finalmente, ejecutemos la aplicación usando el puerto que configuramos anteriormente:

1
app.listen(port, () => console.log(`Listening on port ${port}...`));

Validación y carga de archivos

Por razones fundamentales de seguridad, queremos validar los archivos antes de subirlos a nuestros servidores. Editemos el archivo app.js y agreguemos ambas funcionalidades:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
app.post('/upload-profile-pic', (req, res) => {
    // 'profile_pic' is the name of our file input field in the HTML form
    let upload = multer({ storage: storage, fileFilter: helpers.imageFilter }).single('profile_pic');

    upload(req, res, function(err) {
        // req.file contains information of uploaded file
        // req.body contains information of text fields, if there were any

        if (req.fileValidationError) {
            return res.send(req.fileValidationError);
        }
        else if (!req.file) {
            return res.send('Please select an image to upload');
        }
        else if (err instanceof multer.MulterError) {
            return res.send(err);
        }
        else if (err) {
            return res.send(err);
        }

        // Display uploaded image for user validation
        res.send(`You have uploaded this image: <hr/><img src="${req.file.path}" width="500"><hr /><a href="./">Upload another image</a>`);
    });
});

Aquí, hemos aceptado una solicitud HTTP POST, en la que se incluye la información de la imagen. La función que realmente se ocupa de la funcionalidad de carga es el método multer().single().

Es posible que haya notado fileFilter: helpers.imageFilter pero aún no hemos creado/importado el archivo helpers. Entonces, vamos a crear un nuevo archivo en nuestro directorio de proyectos y nombrarlo helpers.js. Aquí escribiremos un código que se usa para verificar si el archivo enviado es una imagen o no.

1
2
3
4
5
6
7
8
9
const imageFilter = function(req, file, cb) {
    // Accept images only
    if (!file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG|gif|GIF)$/)) {
        req.fileValidationError = 'Only image files are allowed!';
        return cb(new Error('Only image files are allowed!'), false);
    }
    cb(null, true);
};
exports.imageFilter = imageFilter;

Por supuesto, para usar este módulo, tendremos que importarlo en la parte superior de nuestro archivo app.js:

1
const helpers = require('./helpers');

Ahora, podemos ejecutar nuestra aplicación y validar que esté funcionando correctamente:

Uploaded single image

Subir varios archivos

Cargar varios archivos es esencialmente lo mismo que cargar un solo archivo. Sin embargo, en lugar de la función multer().single(), usamos la función multer().array():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app.post('/upload-multiple-images', (req, res) => {
    // 10 is the limit I've defined for number of uploaded files at once
    // 'multiple_images' is the name of our file input field
    let upload = multer({ storage: storage, fileFilter: helpers.imageFilter }).array('multiple_images', 10);

    upload(req, res, function(err) {
        if (req.fileValidationError) {
            return res.send(req.fileValidationError);
        }
        else if (...) // The same as when uploading single images

        let result = "You have uploaded these images: <hr />";
        const files = req.files;
        let index, len;

        // Loop through all the uploaded images and display them on frontend
        for (index = 0, len = files.length; index < len; ++index) {
            result += `<img src="${files[index].path}" width="300" style="margin-right: 20px;">`;
        }
        result += '<hr/><a href="./">Upload more images</a>';
        res.send(result);
    });
});

Y ahora, para validar si todo funciona correctamente:

Uploaded multiple image

Conclusión

Los usuarios no solo consumen datos, sino que los producen y, en muchos casos, necesitan cargarlos en un servidor web. Pueden enviar datos a través de aplicaciones como mensajeros o correos electrónicos a destinatarios específicos, o pueden cargar archivos en redes sociales y plataformas de transmisión de datos como Facebook o YouTube.

En este artículo, hemos usado Express.js y la biblioteca Multer para manejar la funcionalidad básica de carga de archivos en una aplicación web simple.