Volver a explorar Snippets

API Proxy para Descargas Seguras (Evitando bloqueos de Blob/Data-URL)

JAVASCRIPT 13 de abril de 2026 80 lecturas
Solución robusta para forzar descargas de archivos dinámicos (QR, PDF, imágenes) desde el backend. Utiliza cabeceras Content-Disposition para garantizar compatibilidad total en Edge, Chrome y Safari.

Superando las Limitaciones del Navegador: Arquitectura de un Proxy de Descargas

En el desarrollo web moderno, es muy común generar archivos dinámicamente en el lado del cliente (frontend). Herramientas como conversores de imágenes, editores de PDF o generadores de códigos QR suelen renderizar su resultado en un elemento HTML5 <canvas> y luego exportarlo como una cadena codificada en Base64 (Data-URL).

El enfoque tradicional Junior para descargar estos archivos es inyectar esa inmensa cadena de texto en el atributo href de una etiqueta <a> con el atributo download. Sin embargo, esto presenta fallas críticas en producción: navegadores estrictos como iOS Safari bloquean estas descargas por seguridad, y las cadenas Base64 excesivamente largas provocan desbordamientos de memoria (Memory Leaks) que crashean la pestaña del navegador.

Para garantizar una compatibilidad del 100% en cualquier dispositivo, la solución arquitectónica es construir un API Proxy de Descargas en Node.js.

El Flujo Inverso: Del Cliente al Servidor y de Regreso

Este snippet implementa un endpoint transaccional en Express que traslada la responsabilidad de la descarga del navegador al servidor. El funcionamiento se divide en tres etapas de procesamiento:

  1. Decodificación Binaria (Buffer): El cliente envía el archivo en formato de texto (Base64). El servidor utiliza el método split para eliminar el prefijo MIME (data:image/png;base64,) y luego emplea la clase nativa Buffer.from() de Node.js para traducir esa cadena de texto de vuelta a su estado binario puro (ceros y unos), preparándolo para la transmisión.
  2. Inyección de Cabeceras HTTP (El núcleo del Snippet): Para que el navegador entienda que está recibiendo un archivo y no una simple respuesta JSON, configuramos cabeceras estrictas utilizando res.writeHead(). La cabecera Content-Length es vital, ya que le permite al navegador mostrar una barra de progreso real al usuario.
  3. El Disparador de Descarga (Content-Disposition): Esta es la directiva mágica. Al establecer su valor en attachment; filename="...", anulamos el comportamiento predeterminado del navegador (que intentaría abrir la imagen o el PDF en una nueva pestaña) y lo forzamos a abrir el cuadro de diálogo nativo de "Guardar archivo como...", inyectando dinámicamente el nombre exacto que deseamos.
Integración con el Frontend (DX): En la parte inferior del snippet, incluí la lógica de consumo usando la Fetch API. En lugar de esperar un JSON, el frontend convierte la respuesta del servidor en un objeto Blob (Binary Large Object) y genera una URL local efímera mediante window.URL.createObjectURL(). Esta URL es minúscula y perfectamente segura, permitiendo que el cliente dispare la descarga final sin ningún riesgo de bloqueo cruzado o problemas de memoria.

Implementar este patrón Proxy no solo resuelve un bug común de UI, sino que centraliza el tráfico de archivos, abriendo la puerta a futuras mejoras del backend como la compresión al vuelo (GZIP) o el escaneo de virus antes de que el archivo toque el disco duro del usuario.

/**
 * 🚀 SERVER-SIDE DOWNLOAD PROXY
 * Resuelve problemas de descarga en archivos generados por el cliente (Base64).
 */

const express = require('express');
const router = express.Router();

router.post('/api/download', (req, res) => {
    const { dataUrl, fileName, mimeType } = req.body;

    try {
        // 1. Extraemos los datos base64 eliminando el prefijo de la URL
        const base64Data = dataUrl.split(';base64,').pop();
        const fileBuffer = Buffer.from(base64Data, 'base64');

        // 2. Configuramos las cabeceras de respuesta de nivel profesional
        res.writeHead(200, {
            'Content-Type': mimeType,
            'Content-Length': fileBuffer.length,
            // 💎 Clave: Fuerza al navegador a descargar con un nombre específico
            'Content-Disposition': `attachment; filename="${fileName}"`,
            'Cache-Control': 'no-cache'
        });

        // 3. Enviamos el buffer directamente al flujo de respuesta
        res.end(fileBuffer);

    } catch (error) {
        console.error('❌ Error en el Proxy de Descarga:', error);
        res.status(500).json({ error: 'No se pudo procesar la descarga' });
    }
});

module.exports = router;



/* 💡 USO EN FRONTEND (Fetch API): */
   fetch('/api/download', {
       method: 'POST',
       body: JSON.stringify({ dataUrl, fileName: 'mi-qr-premium.png', mimeType: 'image/png' }),
       headers: { 'Content-Type': 'application/json' }
   }).then(res => res.blob()).then(blob => {
       const url = window.URL.createObjectURL(blob);
       // ... lógica de trigger de descarga
   });
¿Qué te pareció?
🔥 Brillante 0
💡 Me sirvió 0
🚀 A otro nivel 0