Este post es una actualización del artículo original “Subir archivos usando PHP, jQuery y Bootstrap” publicado en este blog hace ya varios años. En esta nueva versión, exploramos un enfoque moderno utilizando JavaScript y la API Fetch, permitiendo subir archivos de forma asíncrona sin recargar la página. Este método, que prescinde de jQuery, se adapta a las necesidades actuales de desarrollo web, facilitando una experiencia más rápida y fluida para los usuarios.
Para cargar archivos usando JavaScript, puedes usar la API fetch
en combinación con FormData
. Este enfoque permite enviar archivos de forma asíncrona a un servidor, como una imagen o documento, sin recargar la página. A continuación, un tutorial sobre cómo subir archivos usando fetch
en JavaScript.
Prerrequisitos
Un servidor que pueda manejar la carga de archivos. Para este tutorial, asumimos que tienes un script del lado del servidor (por ejemplo, en PHP o Node.js) que pueda procesar el archivo.
Estructura HTML
Primero, necesitamos un formulario HTML donde el usuario pueda seleccionar un archivo para cargar. Para este ejemplo vamos a crear un archivo llamado (index.html
) con la estructura siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Subir archivos con API Fetch JS</title> <link rel="stylesheet" href="styles.css"> </head> <body> <form id="uploadForm"> <h1>Subir archivo</h1> <label for="fileInput">Selecciona un archivo:</label> <input type="file" id="fileInput" name="file" required> <button type="submit">Subir</button> <p id="responseMessage"></p> </form> <script src="upload.js"></script> </body> </html> |
En este HTML tenemos:
- Un campo de entrada de archivo donde el usuario puede seleccionar un archivo.
- Un botón de envío que activa la carga del archivo cuando se hace clic.
- Un elemento de párrafo para mostrar mensajes, como mensajes de éxito o de error.
CSS: Para dar aspecto mas elegante al formulario.
Ahora, crea un archivo CSS (styles.js
) para manejar darle un aspecto elegante al formulario.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
/* Reseteo de estilos básicos para que todo se vea consistente */ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Arial', sans-serif; } /* Fondo de la página */ body { background-color: #f4f7fc; display: flex; justify-content: center; align-items: center; height: 50vh; padding: 20px; } /* Contenedor principal del formulario */ form { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 30px; width: 100%; max-width: 400px; } /* Título del formulario */ h1 { font-size: 24px; margin-bottom: 20px; text-align: center; color: #333; } /* Etiqueta de los campos */ label { font-size: 14px; color: #666; margin-bottom: 8px; display: inline-block; } /* Estilo del input de tipo archivo */ input[type="file"] { display: block; margin-bottom: 15px; padding: 8px; width: 100%; font-size: 16px; border: 2px solid #ddd; border-radius: 4px; background-color: #f9f9f9; cursor: pointer; transition: border-color 0.3s ease, background-color 0.3s ease; } input[type="file"]:hover { border-color: #007bff; background-color: #e6f7ff; } /* Estilo del botón de envío */ button { background-color: #007bff; color: #fff; border: none; border-radius: 4px; padding: 10px 15px; font-size: 16px; width: 100%; cursor: pointer; transition: background-color 0.3s ease; } button:hover { background-color: #0056b3; } /* Mensaje de respuesta */ #responseMessage { text-align: center; margin-top: 20px; font-size: 16px; color: #555; } /* Estilos para móviles y pantallas pequeñas */ @media (max-width: 500px) { form { padding: 20px; width: 90%; } h1 { font-size: 20px; } button { font-size: 14px; } } /* Estilo para el mensaje de respuesta */ #responseMessage { text-align: center; margin-top: 20px; font-size: 16px; padding: 10px; border-radius: 4px; transition: background-color 0.3s ease, color 0.3s ease; } /* Estilo para mensaje de éxito */ .success { background-color: #d4edda; /* Verde claro */ color: #155724; /* Verde oscuro */ border: 1px solid #c3e6cb; } /* Estilo para mensaje de error */ .error { background-color: #f8d7da; /* Rojo claro */ color: #721c24; /* Rojo oscuro */ border: 1px solid #f5c6cb; } |
JavaScript: Manejo de la Carga de Archivos con Fetch
Ahora, crea un archivo JavaScript (upload.js
) para manejar la carga de archivos usando la API fetch
.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// Esperar a que el documento esté listo document.getElementById('uploadForm').addEventListener('submit', function(event) { event.preventDefault(); // Prevenir que el formulario se envíe de manera normal const fileInput = document.getElementById('fileInput'); const file = fileInput.files[0]; // Obtener el archivo seleccionado if (!file) { displayResponseMessage('Por favor selecciona un archivo!', 'error'); return; } // Crear un objeto FormData para enviar el archivo en la solicitud const formData = new FormData(); formData.append('file', file); // Añadir el archivo al FormData // Usar la API Fetch para enviar el archivo al servidor fetch('upload.php', { method: 'POST', body: formData, // Enviar el FormData con el archivo }) .then(response => response.json()) // Suponiendo que el servidor responde con JSON .then(data => { if (data.success) { displayResponseMessage('Archivo subido correctamente!', 'success'); } else { displayResponseMessage('Subir archivo falló. ' + data.message, 'error'); } }) .catch(error => { displayResponseMessage('Error subiendo el archivo: ' + error.message, 'error'); }); }); // Función para mostrar el mensaje de respuesta function displayResponseMessage(message, type) { const responseMessageElement = document.getElementById('responseMessage'); responseMessageElement.textContent = message; // Eliminar cualquier clase anterior responseMessageElement.classList.remove('success', 'error'); // Añadir la clase apropiada según el tipo de mensaje responseMessageElement.classList.add(type); } |
Explicación:
- FormData: Usamos el objeto
FormData
para recopilar el archivo y cualquier otro dato del formulario (si es necesario). Agregamos el archivo seleccionado aformData
usandoformData.append('file', file)
. - Solicitud Fetch: Enviamos los datos del formulario al servidor usando
fetch
. El cuerpo de la solicitud contiene el objetoFormData
.fetch('upload.php', {...})
: Envía el archivo aupload.php
. Debes reemplazarupload.php
con la URL real del servidor que manejará la carga del archivo. - Manejo de Respuesta: Después de cargar el archivo, el servidor normalmente responde con un objeto JSON. Suponemos que el servidor responde con un mensaje de éxito como este:
1 |
{ "success": true, "message": "¡Archivo subido correctamente!" } |
- Verificamos si la respuesta es exitosa y mostramos el mensaje apropiado al usuario.
- Manejo de Errores: Si ocurre un error durante el proceso de carga, lo capturamos y mostramos un mensaje de error.
Lado del Servidor: Ejemplo de Script PHP para Manejar la Carga del Archivo
El siguiente es un ejemplo de un script PHP (upload.php
) que maneja la carga del archivo desde la solicitud fetch
.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<?php // Configuración $uploadDir = 'uploads/'; // Directorio de destino $maxFileSize = 2 * 1024 * 1024; // Tamaño máximo de archivo en bytes (2 MB) $allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']; // Tipos de archivo permitidos // Comprobamos si se ha subido un archivo if (isset($_FILES['file']) && $_FILES['file']['error'] == UPLOAD_ERR_OK) { $fileTmpPath = $_FILES['file']['tmp_name']; $fileName = $_FILES['file']['name']; $fileSize = $_FILES['file']['size']; $fileType = mime_content_type($fileTmpPath); // Obtenemos el tipo MIME real del archivo // Validación del tipo de archivo if (!in_array($fileType, $allowedTypes)) { echo json_encode(['success' => false, 'message' => 'Tipo de archivo no permitido.']); exit; } // Validación del tamaño del archivo if ($fileSize > $maxFileSize) { echo json_encode(['success' => false, 'message' => 'El archivo es demasiado grande. Tamaño máximo permitido: 2 MB']); exit; } // Crear un nombre de archivo único para evitar sobrescrituras $uniqueFileName = uniqid() . '_' . basename($fileName); $targetFilePath = $uploadDir . $uniqueFileName; // Intentamos mover el archivo al directorio de destino if (move_uploaded_file($fileTmpPath, $targetFilePath)) { echo json_encode(['success' => true, 'message' => 'Archivo subido correctamente']); } else { echo json_encode(['success' => false, 'message' => 'Error al mover el archivo subido']); } } else { // Error si no se ha subido un archivo o hay un problema con la subida echo json_encode(['success' => false, 'message' => 'No se ha subido ningún archivo o hubo un error en la subida']); } ?> |
Explicación del Código
- Configuración Inicial:
- Define el directorio
uploads/
donde se guardarán los archivos. - Establece un límite de tamaño de archivo (2 MB en este ejemplo).
- Define una lista de tipos MIME permitidos para que solo se acepten imágenes JPEG y PNG, además de archivos PDF.
- Define el directorio
- Validación del Tipo de Archivo:
- Utiliza
mime_content_type($fileTmpPath)
para obtener el tipo MIME real del archivo cargado. Esto previene que un archivo malicioso se cargue con una extensión engañosa. - Comprueba si el tipo MIME del archivo está en la lista de tipos permitidos (
$allowedTypes
). Si no, devuelve un mensaje de error.
- Utiliza
- Validación del Tamaño del Archivo:
- Verifica si el tamaño del archivo excede el límite establecido. Si es así, devuelve un mensaje de error.
- Renombrado Único de Archivos:
- Crea un nombre de archivo único con
uniqid()
, lo que evita que archivos con el mismo nombre sobrescriban otros en el directorio de destino.
- Crea un nombre de archivo único con
- Manejo de Errores y Respuestas:
- Cada posible error (tipo de archivo no permitido, tamaño excesivo, etc.) genera un mensaje específico para informar al usuario del problema.
- Si la carga es exitosa, envía un mensaje de éxito.
Prueba de la Carga de Archivos
- Abre
index.html
en un navegador. - Selecciona un archivo para cargar y haz clic en el botón “Subir”.
- Verifica el mensaje debajo del formulario para ver si la carga fue exitosa.
- Confirma que el archivo se ha subido al directorio
uploads/
en tu servidor.
Notas:
- Para que el servidor pueda almacenar archivos cargados por el usuario en un directorio, es necesario que ese directorio tenga permisos de escritura. Si el servidor no tiene permisos para escribir en el directorio
uploads/
, los archivos no se podrán guardar, y la carga fallará. - Por razones de seguridad, debes validar y sanear los archivos cargados (por ejemplo, comprobando tipos y tamaños de archivo) para evitar cargas maliciosas.
Conclusión
La carga de archivos es una funcionalidad poderosa, pero también presenta riesgos significativos si no se maneja adecuadamente. Configurar correctamente los permisos de directorio y aplicar una estricta validación y saneamiento de los archivos son pasos esenciales para proteger tanto el servidor como la aplicación. Estos controles ayudan a evitar abusos del sistema y mitigan las vulnerabilidades que podrían ser explotadas por usuarios malintencionados.
Con una configuración de permisos adecuada y validaciones de archivos, puedes ofrecer una experiencia segura y confiable de carga de archivos a los usuarios, minimizando los riesgos de seguridad y manteniendo la integridad de tu sistema.