File Handling & Operations

Upload, download, and manipulate files with comprehensive file operations

Intermediate 25 min read

Comprehensive File Operations

Apisto provides extensive file handling capabilities including uploads, downloads, format conversions, and progress tracking for all file types.

💡 File Support: Apisto handles images, documents, videos, and any file type with automatic MIME type detection and proper Content-Type headers.

File Downloads

Download files in various formats with automatic type detection and conversion.

📥 Basic Downloads


// Get file as blob with metadata
const fileInfo = await api.getFile('/documents/report.pdf');
console.log('File info:', {
  filename: fileInfo.filename,
  contentType: fileInfo.contentType,
  size: fileInfo.size,
  url: fileInfo.url // Object URL for immediate use
});

// Use the file directly
const link = document.createElement('a');
link.href = fileInfo.url;
link.download = fileInfo.filename;
link.click();

// Clean up object URL
URL.revokeObjectURL(fileInfo.url);
            

🔄 Format Conversions


// Download as different formats
const imageBlob = await api.getFileAsBlob('/images/photo.jpg');
const imageBase64 = await api.getFileAsBase64('/images/photo.jpg');
const textContent = await api.getFileAsText('/documents/readme.txt');
const binaryBuffer = await api.getFileAsBuffer('/files/data.bin');

// Use base64 images directly
document.getElementById('avatar').src = await api.getFileAsBase64('/images/avatar.jpg');

// Process text files
const csvText = await api.getFileAsText('/reports/data.csv');
const csvData = Papa.parse(csvText); // Parse CSV

// Handle binary data
const buffer = await api.getFileAsBuffer('/files/archive.zip');
const uint8Array = new Uint8Array(buffer);
            

🎯 Advanced Download Utility


class FileDownloader {
  constructor(api) {
    this.api = api;
  }

  async downloadAndProcess(url, options = {}) {
    const fileInfo = await this.api.getFile(url);
    
    // Process based on file type
    switch (fileInfo.contentType) {
      case 'application/pdf':
        return await this.processPDF(fileInfo);
      case 'image/jpeg':
      case 'image/png':
        return await this.processImage(fileInfo);
      case 'text/csv':
        return await this.processCSV(fileInfo);
      default:
        return fileInfo;
    }
  }

  async processPDF(fileInfo) {
    // PDF.js or similar processing
    console.log('Processing PDF:', fileInfo.filename);
    return {
      type: 'pdf',
      pages: 'unknown', // Would use PDF.js to get page count
      ...fileInfo
    };
  }

  async processImage(fileInfo) {
    // Create thumbnail or process image
    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => {
        resolve({
          type: 'image',
          width: img.width,
          height: img.height,
          aspectRatio: img.width / img.height,
          ...fileInfo
        });
      };
      img.src = fileInfo.url;
    });
  }

  async processCSV(fileInfo) {
    const text = await this.api.getFileAsText(fileInfo.url);
    const data = Papa.parse(text, { header: true });
    return {
      type: 'csv',
      rows: data.data.length,
      columns: data.meta.fields,
      data: data.data,
      ...fileInfo
    };
  }
}

// Usage
const downloader = new FileDownloader(api);
const processedFile = await downloader.downloadAndProcess('/reports/data.csv');
console.log('Processed file:', processedFile);
          

File Uploads

Upload single or multiple files with progress tracking and automatic FormData handling.

📤 Single File Upload


// Basic file upload
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];

const result = await api.postFile('/upload', file, {
  description: 'User profile picture',
  category: 'avatars'
});

// Upload with additional fields
const uploadResult = await api.postFile('/documents', file, {
  title: 'Project Report',
  tags: ['important', 'q1-2024'],
  visibility: 'private',
  uploadedBy: 'user123'
});

// Using different HTTP methods
await api.putFile('/files/123', file, { 
  reason: 'updated_version' 
});

await api.patchFile('/files/123', null, { 
  description: 'New description' 
}); // No file, just metadata
            

📚 Multiple File Upload


// Upload multiple files at once
const fileInput = document.getElementById('multiFileInput');
const files = Array.from(fileInput.files);

const result = await api.postFile('/uploads/batch', files, {
  album: 'Vacation Photos',
  description: 'Summer 2024 collection',
  category: 'personal'
});

// Upload files with different field names
await api.postFile('/user/profile', {
  avatar: document.getElementById('avatar').files[0],
  coverPhoto: document.getElementById('cover').files[0],
  documents: document.getElementById('docs').files[0]
}, {
  userId: '123',
  updateType: 'full_profile'
});

// Using uploadMultiple method for more control
await api.uploadMultiple('/gallery/upload', files, 'POST', {
  galleryId: 'gallery-123',
  public: false
}, {
  onProgress: (progress) => {
    console.log(`Upload progress: ${progress.percentage}%`);
  }
});
            

Progress Tracking

Monitor upload and download progress with real-time updates and speed calculation.


// Upload with progress tracking
async function uploadWithProgress(file, onProgress) {
  try {
    const result = await api.postFile('/upload', file, {}, {
      onProgress: (progress) => {
        console.log(`Upload: ${progress.percentage.toFixed(1)}%`);
        console.log(`Speed: ${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`);
        
        // Update UI
        updateProgressBar(progress.percentage);
        updateSpeedDisplay(progress.speed);
        
        if (onProgress) onProgress(progress);
      }
    });
    
    console.log('Upload completed:', result);
    return result;
  } catch (error) {
    console.error('Upload failed:', error);
    throw error;
  }
}

// UI integration example
function createUploadUI() {
  const progressBar = document.getElementById('progressBar');
  const progressText = document.getElementById('progressText');
  const speedText = document.getElementById('speedText');
  const cancelButton = document.getElementById('cancelButton');
  
  let currentUpload = null;
  
  cancelButton.addEventListener('click', () => {
    if (currentUpload) {
      currentUpload.cancel();
    }
  });
  
  document.getElementById('uploadForm').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const file = document.getElementById('fileInput').files[0];
    if (!file) return;
    
    try {
      currentUpload = await api.upload.upload('/files', file, {}, {
        onProgress: (progress) => {
          progressBar.style.width = `${progress.percentage}%`;
          progressText.textContent = `${progress.percentage.toFixed(1)}%`;
          speedText.textContent = `${(progress.speed / 1024 / 1024).toFixed(2)} MB/s`;
          
          // Change color when complete
          if (progress.percentage === 100) {
            progressBar.classList.add('bg-green-500');
          }
        }
      });
      
      const result = await currentUpload.promise;
      console.log('Upload successful:', result);
      showSuccessMessage('File uploaded successfully!');
      
    } catch (error) {
      if (error.code === 'UPLOAD_CANCELLED') {
        showErrorMessage('Upload cancelled by user');
      } else {
        showErrorMessage('Upload failed: ' + error.message);
      }
    } finally {
      currentUpload = null;
    }
  });
}

// Format speed for display
function formatSpeed(bytesPerSecond) {
  if (bytesPerSecond < 1024) {
    return `${bytesPerSecond.toFixed(0)} B/s`;
  } else if (bytesPerSecond < 1024 * 1024) {
    return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
  } else {
    return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`;
  }
}
          

Chunked Uploads for Large Files

Upload large files in manageable chunks with resume capabilities and error recovery.


// Chunked upload for large files
async function uploadLargeFile(largeFile, onProgress) {
  console.log(`Starting chunked upload for: ${largeFile.name} (${(largeFile.size / 1024 / 1024).toFixed(2)} MB)`);
  
  const upload = await api.upload.uploadChunked('/files/large', largeFile, {
    chunkSize: 5 * 1024 * 1024, // 5MB chunks
    fields: {
      description: '4K Video File',
      category: 'videos',
      originalName: largeFile.name
    },
    finalizeEndpoint: '/files/finalize', // Called when all chunks are uploaded
    maxChunkRetries: 3,
    onProgress: (progress) => {
      const details = `
Overall: ${progress.percentage.toFixed(1)}%
Chunk ${progress.chunk}/${progress.totalChunks}: ${progress.chunkProgress.toFixed(1)}%
Speed: ${formatSpeed(progress.bytesPerSecond)}
      `.trim();
      
      console.log(details);
      
      if (onProgress) {
        onProgress({
          ...progress,
          details: `Chunk ${progress.chunk}/${progress.totalChunks}`
        });
      }
    }
  });
  
  // You can control the upload
  // upload.pause(); // Pause the entire upload
  // upload.resume(); // Resume from where it left off
  // upload.cancel(); // Cancel the upload
  // upload.retryChunk(5); // Retry specific chunk
  
  try {
    const result = await upload.promise;
    console.log('Chunked upload completed:', result);
    return result;
  } catch (error) {
    console.error('Chunked upload failed:', error);
    throw error;
  }
}

// Resume functionality
class ResumableUpload {
  constructor(api, file, endpoint) {
    this.api = api;
    this.file = file;
    this.endpoint = endpoint;
    this.uploadId = null;
    this.uploadedChunks = new Set();
  }
  
  async startOrResume(onProgress) {
    // Check if there's an existing upload to resume
    const existingUpload = await this.checkExistingUpload();
    
    if (existingUpload) {
      console.log('Resuming existing upload:', existingUpload.uploadId);
      this.uploadId = existingUpload.uploadId;
      this.uploadedChunks = new Set(existingUpload.uploadedChunks);
    }
    
    return await this.api.upload.uploadChunked(this.endpoint, this.file, {
      uploadId: this.uploadId,
      onProgress,
      // ... other options
    });
  }
  
  async checkExistingUpload() {
    try {
      // Implementation depends on your backend
      const status = await this.api.get(`/uploads/status?file=${this.file.name}`);
      return status;
    } catch (error) {
      return null;
    }
  }
}
          

File Validation & Security

Validate files before upload with size limits, type restrictions, and security checks.

🛡️ Client-side Validation


class FileValidator {
  static validateFile(file, rules = {}) {
    const {
      maxSize = 10 * 1024 * 1024, // 10MB default
      allowedTypes = [],
      allowedExtensions = [],
      maxDimensions = null // for images
    } = rules;
    
    const errors = [];
    
    // Size validation
    if (file.size > maxSize) {
      errors.push(`File too large: ${(file.size / 1024 / 1024).toFixed(2)}MB (max: ${maxSize / 1024 / 1024}MB)`);
    }
    
    // Type validation
    if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
      errors.push(`File type not allowed: ${file.type}`);
    }
    
    // Extension validation
    if (allowedExtensions.length > 0) {
      const extension = file.name.split('.').pop().toLowerCase();
      if (!allowedExtensions.includes(extension)) {
        errors.push(`File extension not allowed: .${extension}`);
      }
    }
    
    // Image dimension validation (async)
    if (maxDimensions && file.type.startsWith('image/')) {
      return this.validateImageDimensions(file, maxDimensions)
        .then(dimensionErrors => [...errors, ...dimensionErrors]);
    }
    
    return Promise.resolve(errors);
  }
  
  static validateImageDimensions(file, maxDimensions) {
    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => {
        const errors = [];
        if (img.width > maxDimensions.width) {
          errors.push(`Image too wide: ${img.width}px (max: ${maxDimensions.width}px)`);
        }
        if (img.height > maxDimensions.height) {
          errors.push(`Image too tall: ${img.height}px (max: ${maxDimensions.height}px)`);
        }
        resolve(errors);
      };
      img.src = URL.createObjectURL(file);
    });
  }
}

// Usage with Apisto
async function uploadWithValidation(file) {
  const validationErrors = await FileValidator.validateFile(file, {
    maxSize: 5 * 1024 * 1024, // 5MB
    allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
    allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf'],
    maxDimensions: { width: 4000, height: 4000 }
  });
  
  if (validationErrors.length > 0) {
    throw new Error(`Validation failed: ${validationErrors.join(', ')}`);
  }
  
  // Use Apisto's built-in validation
  return await api.validateAndUpload('/files/secure', file, {
    maxSize: 5 * 1024 * 1024,
    allowedTypes: ['image/jpeg', 'image/png', 'application/pdf']
  });
}
            

🔒 Security Best Practices


// Secure file upload service
class SecureFileService {
  constructor(api) {
    this.api = api;
  }
  
  async uploadUserFile(file, userId, purpose) {
    // Generate secure filename
    const secureName = this.generateSecureFilename(file, userId);
    
    // Upload with security headers
    const result = await this.api.postFile('/files/secure', file, {
      secureName,
      userId,
      purpose,
      uploadTime: new Date().toISOString(),
      ip: await this.getClientIP() // From your backend
    }, {
      headers: {
        'X-File-Checksum': await this.calculateChecksum(file),
        'X-Upload-Scope': 'user_upload'
      }
    });
    
    // Log the upload for security auditing
    await this.logUpload({
      fileId: result.fileId,
      userId,
      originalName: file.name,
      secureName,
      size: file.size,
      type: file.type,
      purpose
    });
    
    return result;
  }
  
  generateSecureFilename(file, userId) {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substring(2, 15);
    const extension = file.name.split('.').pop();
    return `user_${userId}_${timestamp}_${random}.${extension}`;
  }
  
  async calculateChecksum(file) {
    // Simple checksum implementation
    const buffer = await file.arrayBuffer();
    const hash = new Uint8Array(buffer).reduce((a, b) => a + b, 0);
    return hash.toString(16);
  }
  
  async getClientIP() {
    // This would be set by your backend
    return 'unknown';
  }
  
  async logUpload(details) {
    // Send to your logging service
    console.log('File upload logged:', details);
  }
}

// Usage
const fileService = new SecureFileService(api);
const uploadResult = await fileService.uploadUserFile(
  file, 
  'user123', 
  'profile_picture'
);
            

Complete File Management System


// Complete file management service
class FileManager {
  constructor(api) {
    this.api = api;
    this.uploads = new Map();
    this.downloads = new Map();
  }
  
  // UPLOAD MANAGEMENT
  async uploadFile(file, options = {}) {
    const uploadId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    const upload = {
      id: uploadId,
      file,
      options,
      progress: 0,
      status: 'pending',
      startTime: Date.now()
    };
    
    this.uploads.set(uploadId, upload);
    
    try {
      const apistoUpload = await this.api.upload.upload('/files', file, {
        folder: options.folder,
        description: options.description,
        tags: options.tags || []
      }, {
        onProgress: (progress) => {
          upload.progress = progress.percentage;
          upload.status = 'uploading';
          upload.speed = progress.bytesPerSecond;
          
          if (options.onProgress) {
            options.onProgress({
              ...progress,
              uploadId,
              file: upload.file
            });
          }
        }
      });
      
      // Store for control
      upload.apistoUpload = apistoUpload;
      
      const result = await apistoUpload.promise;
      upload.status = 'completed';
      upload.result = result;
      
      return result;
      
    } catch (error) {
      upload.status = 'error';
      upload.error = error;
      throw error;
    }
  }
  
  // DOWNLOAD MANAGEMENT
  async downloadFile(fileId, options = {}) {
    const downloadId = `download_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    const download = {
      id: downloadId,
      fileId,
      options,
      progress: 0,
      status: 'pending',
      startTime: Date.now()
    };
    
    this.downloads.set(downloadId, download);
    
    try {
      const fileInfo = await this.api.getFile(`/files/${fileId}`);
      download.status = 'completed';
      download.fileInfo = fileInfo;
      
      return fileInfo;
      
    } catch (error) {
      download.status = 'error';
      download.error = error;
      throw error;
    }
  }
  
  // BATCH OPERATIONS
  async uploadMultiple(files, options = {}) {
    const uploads = files.map(file => 
      this.uploadFile(file, options)
    );
    
    return Promise.allSettled(uploads);
  }
  
  async downloadMultiple(fileIds, options = {}) {
    const downloads = fileIds.map(fileId =>
      this.downloadFile(fileId, options)
    );
    
    return Promise.allSettled(downloads);
  }
  
  // CONTROL METHODS
  cancelUpload(uploadId) {
    const upload = this.uploads.get(uploadId);
    if (upload?.apistoUpload) {
      upload.apistoUpload.cancel();
      upload.status = 'cancelled';
    }
  }
  
  pauseUpload(uploadId) {
    const upload = this.uploads.get(uploadId);
    if (upload?.apistoUpload) {
      upload.apistoUpload.pause();
      upload.status = 'paused';
    }
  }
  
  resumeUpload(uploadId) {
    const upload = this.uploads.get(uploadId);
    if (upload?.apistoUpload) {
      upload.apistoUpload.resume();
      upload.status = 'uploading';
    }
  }
  
  // STATUS METHODS
  getUploadStatus(uploadId) {
    return this.uploads.get(uploadId);
  }
  
  getDownloadStatus(downloadId) {
    return this.downloads.get(downloadId);
  }
  
  getAllUploads() {
    return Array.from(this.uploads.values());
  }
  
  getAllDownloads() {
    return Array.from(this.downloads.values());
  }
  
  // CLEANUP
  cleanupCompleted() {
    for (const [id, upload] of this.uploads) {
      if (upload.status === 'completed' || upload.status === 'error') {
        this.uploads.delete(id);
      }
    }
    
    for (const [id, download] of this.downloads) {
      if (download.status === 'completed' || download.status === 'error') {
        URL.revokeObjectURL(download.fileInfo?.url);
        this.downloads.delete(id);
      }
    }
  }
}

// Usage example
const fileManager = new FileManager(api);

// Upload a file with progress
const result = await fileManager.uploadFile(file, {
  folder: 'documents',
  description: 'Quarterly report',
  onProgress: (progress) => {
    updateUploadUI(progress);
  }
});

// Download a file
const downloadedFile = await fileManager.downloadFile('file-123');

// Batch operations
const batchResults = await fileManager.uploadMultiple([file1, file2, file3], {
  folder: 'photos',
  onProgress: (progress) => {
    console.log(`File ${progress.uploadId}: ${progress.percentage}%`);
  }
});
        

🎉 Amazing! You've mastered comprehensive file handling with Apisto. You can now build sophisticated file upload/download systems with progress tracking, validation, and security.

Ready to secure your API calls?

Learn how to implement various authentication strategies including JWT, API keys, and OAuth2.

Continue to Authentication →