Implementar subidas de archivos con Bootstrap 5
Las subidas de archivos son esenciales en las aplicaciones web modernas. Bootstrap 5 ofrece herramientas para crear interfaces de subida de archivos elegantes y fáciles de usar. Esta guía te mostrará cómo implementar subidas de archivos con Bootstrap 5, incluida la funcionalidad de arrastrar y soltar, los botones de subida personalizados y la indicación del progreso con AJAX.
Introducción a las subidas de archivos con Bootstrap 5
Bootstrap 5 ofrece una variedad de componentes para crear interfaces web adaptables. Aunque no incluye un componente dedicado a la subida de archivos, puedes usar los controles de formulario de Bootstrap para crear formularios de subida personalizados que se integran sin problemas en tu aplicación.
En este tutorial veremos:
- Configurar un entorno de Bootstrap 5
- Crear un formulario sencillo de subida de archivos
- Mejorarlo con la funcionalidad de arrastrar y soltar
- Subir con AJAX e indicación del progreso
- Gestionar las subidas de archivos en el lado del servidor
- Aplicar buenas prácticas de seguridad y manejo de errores
Configurar un entorno de Bootstrap 5
Para empezar, configura un entorno básico de Bootstrap 5. Puedes incluir Bootstrap mediante CDN o instalarlo localmente.
Incluirlo mediante CDN:
Añade los siguientes enlaces a la sección <head> de tu archivo HTML:
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"
></script>
Incluirlo mediante npm:
Si usas una herramienta de compilación, instala Bootstrap mediante npm:
npm install bootstrap@5.3.3
Crear un formulario sencillo de subida de archivos
Vamos a crear un formulario básico de subida de archivos con los controles de formulario de Bootstrap:
<form action="/upload" method="post" enctype="multipart/form-data">
<div class="mb-3">
<label for="formFile" class="form-label">Choose a file to upload</label>
<input
class="form-control"
type="file"
id="formFile"
name="file"
required
accept="image/jpeg,image/png,application/pdf"
/>
</div>
<div class="progress d-none mb-3">
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
Mejorar con la funcionalidad de arrastrar y soltar
Reemplaza solo el primer grupo de entrada de archivos .mb-3 del formulario anterior por este grupo.
Conserva el formulario contenedor, la barra de progreso y el botón de envío para el manejador de
AJAX que aparece más adelante en esta guía. Coloca el script después del formulario:
<div class="mb-3">
<label for="formFile" class="form-label">Upload your file</label>
<button type="button" id="dropZone" class="w-100 rounded border border-primary p-4 text-center">
Drag and drop a file here or click to select
</button>
<div id="errorMessage" role="alert" class="alert alert-danger d-none mt-2"></div>
<input
type="file"
id="formFile"
name="file"
class="d-none"
accept="image/jpeg,image/png,application/pdf"
/>
</div>
<script>
const dropZone = document.getElementById('dropZone')
const formFile = document.getElementById('formFile')
const errorMessage = document.getElementById('errorMessage')
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']
const maxSize = 5 * 1024 * 1024 // 5MB
function validateFile(file) {
if (!file) {
throw new Error('Please select one file to upload.')
}
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid file type. Please upload JPEG, PNG or PDF files.')
}
if (file.size > maxSize) {
throw new Error('File too large. Maximum size is 5MB.')
}
}
function showError(message) {
errorMessage.textContent = message
errorMessage.classList.remove('d-none')
}
function hideError() {
errorMessage.classList.add('d-none')
}
dropZone.addEventListener('click', () => formFile.click())
dropZone.addEventListener('dragover', (e) => {
e.preventDefault()
dropZone.classList.add('bg-light')
})
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('bg-light')
})
dropZone.addEventListener('drop', (e) => {
e.preventDefault()
dropZone.classList.remove('bg-light')
hideError()
try {
const droppedFiles = e.dataTransfer?.files
if (!droppedFiles || droppedFiles.length !== 1) {
throw new Error('Please drop exactly one file.')
}
const file = droppedFiles[0]
validateFile(file)
formFile.files = droppedFiles
updateDropZoneText()
} catch (error) {
formFile.value = ''
updateDropZoneText()
showError(error.message)
}
})
formFile.addEventListener('change', () => {
hideError()
try {
if (formFile.files.length > 0) {
validateFile(formFile.files[0])
updateDropZoneText()
}
} catch (error) {
showError(error.message)
formFile.value = ''
updateDropZoneText()
}
})
function updateDropZoneText() {
dropZone.textContent =
formFile.files.length > 0
? formFile.files[0].name
: 'Drag and drop a file here or click to select'
}
</script>
Gestionar las subidas de archivos en el lado del servidor
Instala Express y Multer usando la versión corregida de Multer documentada en su aviso de seguridad:
npm install express@5 multer@2.3.0
Este servidor gestiona /upload. Sirve el HTML y los scripts desde el mismo origen en tu aplicación;
mantén uploads/ fuera de cualquier directorio expuesto por el middleware de archivos estáticos. El
filtro MIME de abajo solo comprueba los metadatos proporcionados por el cliente. Inspecciona el
contenido de los archivos y aplica la autorización antes de poner los archivos almacenados a
disposición de los usuarios.
const express = require('express')
const multer = require('multer')
const path = require('path')
const app = express()
const storage = multer.diskStorage({
destination: 'uploads/',
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9)
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname))
},
})
const fileFilter = (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']
if (!allowedTypes.includes(file.mimetype)) {
cb(new Error('Invalid file type'), false)
return
}
cb(null, true)
}
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 1,
fields: 0,
parts: 2,
},
})
app.post('/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded.' })
}
res.json({ message: 'File uploaded successfully' })
})
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
const status = error.code === 'LIMIT_FILE_SIZE' ? 413 : 400
return res.status(status).json({ error: 'File exceeds an upload limit.' })
}
if (error.message === 'Invalid file type') {
return res.status(400).json({ error: 'Unsupported file type.' })
}
console.error('Upload failed', { code: error.code ?? 'UNKNOWN' })
res.status(500).json({ error: 'Unable to upload the file.' })
})
app.listen(3000, () => {
console.log('Server started on http://localhost:3000')
})
Subir con AJAX e indicación del progreso
Para mejorar la experiencia de usuario, puedes subir archivos de forma asíncrona y ofrecer información en tiempo real sobre el progreso de la subida. El siguiente ejemplo muestra cómo interceptar el envío de un formulario, enviar el archivo mediante XMLHttpRequest y actualizar una barra de progreso de Bootstrap en tiempo real:
Este manejador se basa en la versión de arrastrar y soltar. Ejecútalo después de ese script para que validateFile() esté disponible.
Llegar al 100 % significa que se envió el cuerpo de la solicitud; el éxito todavía requiere una
respuesta correcta del servidor.
const form = document.querySelector('form')
const progressBarEl = document.querySelector('.progress')
const progressFill = document.querySelector('.progress-bar')
form.addEventListener('submit', (e) => {
e.preventDefault()
try {
validateFile(form.querySelector('input[type="file"]').files[0])
} catch (error) {
alert(error.message)
return
}
const formData = new FormData(form)
const xhr = new XMLHttpRequest()
xhr.open('POST', form.getAttribute('action'), true)
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable && event.total > 0) {
const percentage = (event.loaded / event.total) * 100
progressFill.style.width = percentage + '%'
progressFill.textContent = Math.round(percentage) + '%'
progressBarEl.classList.remove('d-none')
}
})
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
alert('File uploaded successfully')
} else {
alert('File upload failed')
}
progressBarEl.classList.add('d-none')
progressFill.style.width = '0%'
progressFill.textContent = ''
}
xhr.onerror = () => {
alert('Upload error')
progressBarEl.classList.add('d-none')
progressFill.style.width = '0%'
progressFill.textContent = ''
}
xhr.send(formData)
})
Consideraciones de seguridad
Al implementar subidas de archivos, ten en cuenta estas medidas de seguridad:
- Validar los tipos de archivo tanto en el lado del cliente como en el del servidor
- Implementar límites de tamaño de archivo
- Usar ubicaciones seguras de almacenamiento de archivos
- Implementar limitación de tasa en las subidas para evitar abusos
- Analizar los archivos subidos en busca de malware
- Usar protección CSRF
- Implementar un manejo de errores adecuado, incluido el registro de errores para la supervisión de seguridad
Conclusión
Al combinar Bootstrap 5 con técnicas robustas de subida de archivos y medidas de seguridad adecuadas, puedes crear un sistema de subida de archivos fluido y fácil de usar. La implementación incluye la funcionalidad de arrastrar y soltar, la indicación del progreso basada en AJAX y un manejo de errores completo.
Para una solución lista para producción que gestione las subidas y el procesamiento de archivos a gran escala, considera Transloadit. Transloadit ofrece servicios fiables de subida y encoding de archivos que pueden agilizar tu proceso de desarrollo y encargarse de tareas complejas de procesamiento de archivos.
