Express
Tutorial Express: Upload de archivos con Multer
Aprende a configurar Multer para subir archivos en Express con Node.js. Controla almacenamiento, validación y manejo de FormData fácilmente.
Aprende Express y certifícateConfiguración de Multer
Multer es el middleware estándar para manejar uploads de archivos en aplicaciones Express. Este paquete procesa formularios multipart/form-data
y proporciona un control granular sobre cómo y dónde se almacenan los archivos subidos.
La configuración de Multer se basa en opciones de almacenamiento que determinan el destino y el nombre de los archivos. Express 5 mantiene total compatibilidad con Multer, permitiendo integrarlo de forma nativa en el pipeline de middlewares.
Instalación y configuración básica
Para comenzar, instalamos Multer como dependencia del proyecto:
npm install multer
La configuración más simple utiliza el almacenamiento en disco (diskStorage
), que guarda los archivos directamente en el sistema de archivos del servidor:
import multer from 'multer';
import path from 'path';
// Configuración básica de almacenamiento
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // Carpeta donde se guardarán los archivos
},
filename: function (req, file, cb) {
// Genera un nombre único para evitar conflictos
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({ storage: storage });
Opciones de configuración avanzada
Multer ofrece múltiples opciones de configuración para controlar el comportamiento del upload:
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // Límite de 5MB por archivo
files: 3 // Máximo 3 archivos por request
},
fileFilter: function (req, file, cb) {
// Filtro para aceptar solo imágenes
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Solo se permiten archivos de imagen'), false);
}
}
});
Configuración de almacenamiento en memoria
Para casos donde necesitamos procesar archivos temporalmente sin guardarlos en disco, Multer proporciona almacenamiento en memoria:
const memoryStorage = multer.memoryStorage();
const uploadToMemory = multer({
storage: memoryStorage,
limits: {
fileSize: 2 * 1024 * 1024 // 2MB máximo
}
});
Con esta configuración, los archivos se almacenan como Buffer en la propiedad req.file.buffer
, permitiendo procesarlos directamente sin crear archivos temporales.
Configuración dinámica del destino
Multer permite configurar destinos dinámicos basados en datos del request o características del archivo:
const dynamicStorage = multer.diskStorage({
destination: function (req, file, cb) {
// Crear carpetas por tipo de archivo
let folder = 'uploads/';
if (file.mimetype.startsWith('image/')) {
folder += 'images/';
} else if (file.mimetype.startsWith('video/')) {
folder += 'videos/';
} else {
folder += 'documents/';
}
cb(null, folder);
},
filename: function (req, file, cb) {
// Incluir timestamp y mantener extensión original
const timestamp = new Date().toISOString().replace(/:/g, '-');
const extension = path.extname(file.originalname);
cb(null, `${file.fieldname}-${timestamp}${extension}`);
}
});
Validación y filtros personalizados
La función fileFilter
permite implementar validaciones complejas antes de procesar el archivo:
const customUpload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
// Lista de tipos MIME permitidos
const allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'application/pdf'
];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
const error = new Error(`Tipo de archivo no permitido: ${file.mimetype}`);
error.code = 'INVALID_FILE_TYPE';
cb(error, false);
}
}
});
Configuración para múltiples campos
Multer soporta configuraciones específicas para diferentes campos de archivo en el mismo formulario:
// Configuración para avatar (un solo archivo)
const avatarUpload = multer({
storage: multer.diskStorage({
destination: 'uploads/avatars/',
filename: (req, file, cb) => {
cb(null, `avatar-${req.user.id}-${Date.now()}.jpg`);
}
}),
limits: { fileSize: 1024 * 1024 }, // 1MB
fileFilter: (req, file, cb) => {
cb(null, file.mimetype.startsWith('image/'));
}
});
// Configuración para documentos (múltiples archivos)
const documentsUpload = multer({
storage: multer.diskStorage({
destination: 'uploads/documents/',
filename: (req, file, cb) => {
cb(null, `doc-${Date.now()}-${file.originalname}`);
}
}),
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5 // Máximo 5 documentos
}
});
Esta configuración modular permite aplicar diferentes restricciones y comportamientos según el contexto de uso, manteniendo la flexibilidad necesaria para diferentes tipos de uploads en la misma aplicación.
Manejo de FormData
FormData es la interfaz estándar del navegador para construir conjuntos de datos que incluyen archivos y campos de texto. En Express 5, el manejo de FormData con Multer requiere entender cómo los datos se estructuran en el request y cómo acceder a ellos correctamente.
Cuando un formulario HTML utiliza enctype="multipart/form-data"
, los datos se envían en un formato especial que Multer procesa y organiza en diferentes propiedades del objeto req
. Esta separación permite acceder tanto a los archivos subidos como a los campos de texto de forma independiente.
Estructura de datos en el request
Multer organiza los datos del FormData en propiedades específicas del objeto request:
import express from 'express';
import multer from 'multer';
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('archivo'), (req, res) => {
// Información del archivo subido
console.log('Archivo:', req.file);
// Campos de texto del formulario
console.log('Campos:', req.body);
// Información completa del request
console.log('Headers:', req.headers['content-type']);
});
Acceso a campos de texto
Los campos de texto del formulario se mantienen disponibles en req.body
, independientemente de la presencia de archivos:
app.post('/profile', upload.single('avatar'), (req, res) => {
// Datos del formulario de texto
const { nombre, email, descripcion } = req.body;
// Información del archivo subido
const avatar = req.file;
if (!avatar) {
return res.status(400).json({
error: 'Avatar requerido',
receivedData: { nombre, email, descripcion }
});
}
// Procesar datos combinados
const perfil = {
nombre,
email,
descripcion,
avatarPath: avatar.path,
avatarSize: avatar.size
};
res.json({ mensaje: 'Perfil actualizado', perfil });
});
Manejo de múltiples archivos con campos
Cuando el formulario incluye múltiples archivos y campos, Multer organiza los datos manteniendo la separación clara:
// Formulario con múltiples tipos de campos
app.post('/proyecto', upload.fields([
{ name: 'portada', maxCount: 1 },
{ name: 'documentos', maxCount: 5 }
]), (req, res) => {
// Campos de texto del formulario
const { titulo, descripcion, categoria } = req.body;
// Archivos organizados por campo
const portada = req.files['portada'] ? req.files['portada'][0] : null;
const documentos = req.files['documentos'] || [];
// Validación de datos requeridos
if (!titulo || !portada) {
return res.status(400).json({
error: 'Título y portada son requeridos',
received: { titulo, descripcion, categoria }
});
}
// Construcción del objeto proyecto
const proyecto = {
titulo,
descripcion,
categoria,
portada: {
filename: portada.filename,
path: portada.path,
size: portada.size
},
documentos: documentos.map(doc => ({
originalName: doc.originalname,
filename: doc.filename,
size: doc.size
}))
};
res.json({ proyecto });
});
Validación de FormData completo
La validación integral de FormData requiere verificar tanto archivos como campos de texto:
function validarFormularioCompleto(req, res, next) {
const { nombre, email } = req.body;
const archivo = req.file;
// Validar campos de texto
if (!nombre || nombre.trim().length < 2) {
return res.status(400).json({
error: 'Nombre debe tener al menos 2 caracteres'
});
}
if (!email || !email.includes('@')) {
return res.status(400).json({
error: 'Email inválido'
});
}
// Validar archivo
if (!archivo) {
return res.status(400).json({
error: 'Archivo requerido'
});
}
// Validar tamaño y tipo
if (archivo.size > 2 * 1024 * 1024) {
return res.status(400).json({
error: 'Archivo demasiado grande (máximo 2MB)'
});
}
next();
}
app.post('/documento', upload.single('archivo'), validarFormularioCompleto, (req, res) => {
res.json({
mensaje: 'Documento procesado correctamente',
datos: {
usuario: req.body.nombre,
email: req.body.email,
archivo: req.file.filename
}
});
});
Procesamiento de arrays en FormData
Los campos de array en FormData requieren manejo especial, ya que pueden enviarse como múltiples campos con el mismo nombre:
app.post('/encuesta', upload.none(), (req, res) => {
// FormData puede enviar arrays de diferentes formas
console.log('Body completo:', req.body);
// Normalizar arrays (pueden venir como string o array)
const intereses = Array.isArray(req.body.intereses)
? req.body.intereses
: [req.body.intereses].filter(Boolean);
const respuestas = {
nombre: req.body.nombre,
edad: parseInt(req.body.edad),
intereses: intereses,
comentarios: req.body.comentarios || ''
};
res.json({ respuestas });
});
Manejo de errores específicos de FormData
Los errores relacionados con FormData requieren tratamiento específico para proporcionar feedback útil:
app.post('/upload-completo', upload.single('archivo'), (req, res) => {
try {
// Verificar que se recibieron datos
if (!req.body && !req.file) {
return res.status(400).json({
error: 'No se recibieron datos del formulario'
});
}
// Procesar datos recibidos
const resultado = {
archivo: req.file ? {
nombre: req.file.originalname,
tamaño: req.file.size,
tipo: req.file.mimetype
} : null,
campos: Object.keys(req.body).length > 0 ? req.body : null
};
res.json({
mensaje: 'FormData procesado correctamente',
datos: resultado
});
} catch (error) {
res.status(500).json({
error: 'Error procesando FormData',
detalle: error.message
});
}
});
// Middleware global para errores de Multer
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({
error: 'Archivo demasiado grande',
limite: '5MB máximo'
});
}
if (error.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({
error: 'Demasiados archivos',
limite: 'Máximo 3 archivos'
});
}
}
res.status(500).json({
error: 'Error procesando el formulario',
detalle: error.message
});
});
Esta aproximación integral permite manejar FormData complejo manteniendo la separación clara entre archivos y datos de texto, facilitando la validación y el procesamiento posterior de la información recibida.
Otras lecciones de Express
Accede a todas las lecciones de Express y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Expressjs
Introducción Y Entorno
Instalación De Express
Introducción Y Entorno
Estados Http
Routing
Métodos Delete
Routing
Parámetros Y Query Strings
Routing
Métodos Get
Routing
Ejercicios de programación de Express
Evalúa tus conocimientos de esta lección Upload de archivos con Multer con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.