Express.js no permite cargar archivos directamente, así que necesitamos usar una librería llamada multer.
Como siempre, el primer paso es agregar la librería:
$ yarn add multer
El siguiente paso es configurar la librería en nuestro código:
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const app = express();
app.post('/profile', upload.single('avatar'), (req, res) => {
// req.file es el archivo 'avatar', el nombre original se puede obtener
// con req.file.originalname
});
...
En la línea 3 estamos configurando multer para que las imágenes queden almacenadas en la carpeta uploads
. En la línea 7 estamos creando la ruta /profile
y como segundo parámetro le estamos pasando el middleware de multer que va a recibir la imagen del formulario y guardarla en la carpeta uploads
.
El formulario sería el siguiente:
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar">
<input type="submit" value="Enviar">
</form>
En la primera línea estamos definiendo la ruta /profile
con el método POST
. También definimos un formato llamado multipart/form-data
, que nos permite enviar uno o más archivos junto a otra información del formulario. Sin esta última opción la carga de archivos no funcionaría.
En la segunda línea estamos definiendo el campo de tipo archivo que se va a utilizar para cargar la imagen.
Para validar que sólo puedan cargar imágenes podemos agregarle una opción a la configuración de multer:
...
const upload = multer({
dest: 'uploads/',
fileFilter(req, file, next) {
const isPhoto = file.mimetype.startsWith('image/');
if (isPhoto) {
next(null, true);
} else {
next({ message: "El tipo de archivo no es válido" }, false);
}
}
});
También es conveniente agregar la siguiente validación al campo de tipo archivo:
<input type="file" name="avatar" accept="image/gif, image/png, image/jpeg">
De esa forma el usuario no va a poder seleccionar archivos que no sean imágenes en el formulario. Sin embargo, la validación en el servidor es necesaria porque alguien podría modificar el formulario y enviar otro tipo de archivos.
También es posible limitar el tamaño del archivo con la opción fileSize
, que recibe el número máximo de bytes que puede tener el archivo:
const upload = multer({
...,
fileSize: 1000000 // 1MB
});
Si dos usuarios suben imágenes con el mismo nombre la primera imagen se va a sobrescribir. Para evitar ese problema es buena práctica generar nombre de archivos únicos con un paquete llamado uuid.
El primer paso es agregar la librería:
$ yarn add uuid
Ahora debemos modificar el código para cambiar el nombre de las imágenes:
const express = require('express');
const multer = require('multer');
const path = require("path");
const uuid = require("uuid");
const app = express();
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads/');
},
filename(req, file, cb) {
cb(null, uuid.v4() + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage
});
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// ...
});
En la línea 8 estamos definiendo el tipo de almacenamiento que vamos a utilizar para las imágenes, en este caso una carpeta en el disco duro, y definimos dos métodos: destination
y filename
. destination
se utiliza para definir el nombre de la carpeta, y filename
para definir el nombre del archivo, ahí es donde utilizamos la librería uuid para generar un nombre único.
Hasta ahora hemos visto cómo cargar una única imagen. Sin embargo multer también permite cargar varias imágenes a la vez.
app.post('/profile', upload.array('photos'), (req, res) => {
// las imágenes están en req.files (es un arreglo)
});
No es buena idea utilizar las imágenes originales que suben los usuarios porque podrían ser muy pesadas. Es mejor modificar su tamaño para garantizar que no pesen más de lo necesario.
Para eso vamos a utilizar una librería llamada Jimp que nos va a permitir redimensionar las imágenes.
El primer paso es agregar la librería:
$ yarn add jimp
Ahora debemos configurar Jimp en nuestro código:
const express = require('express');
const multer = require('multer');
const app = express();
const uploader = multer(...);
// este es el middleware que va a redimensionar la imagen
const resize = async (req, res, next) => {
if (!req.file) {
next();
return;
}
const photo = await jimp.read(req.file.path);
await photo.resize(1024, jimp.AUTO);
await photo.write(`uploads/big/${req.file.filename}`);
next();
};
// debemos configurar el middleware en la ruta
app.post('/profile', uploader.single('avatar'), resize, (req, res) => {
// req.file es el archivo 'avatar', el nombre original se puede obtener
// con req.file.originalname
});
Fíjate cómo estamos definiendo el método resize
, que realmente es un middleware que vamos a configurar en la ruta POST /profile
que recibe la imagen. Ese método lee el archivo, lo redimensiona y guarda en la carpeta uploads/big/
. Para ver todas las opciones de Jimp consulta la documentación.