Skip to content

Storage

Flexible file storage system with multiple provider support and automatic file handling

Overview

Base Framework's storage system provides a unified interface for file uploads with support for local storage, Amazon S3, and Cloudflare R2. It includes automatic file validation, size limits, and MIME type detection.

Key Features

  • Local Storage - Store files directly on your server's filesystem with automatic directory creation
  • Amazon S3 - Scalable cloud storage with S3-compatible APIs and CDN support
  • Cloudflare R2 - Zero egress fee storage with global CDN and S3 compatibility

Configuration

Environment Variables

bash
# Storage Provider (local, s3, r2)
STORAGE_PROVIDER=local

# Local Storage
STORAGE_PATH=./uploads
STORAGE_BASE_URL=http://localhost:3000/uploads

# Amazon S3
STORAGE_S3_KEY=your-access-key
STORAGE_S3_SECRET=your-secret-key
STORAGE_S3_BUCKET=your-bucket
STORAGE_S3_REGION=us-east-1
STORAGE_S3_CDN=https://cdn.example.com

# Cloudflare R2
STORAGE_R2_ACCOUNT_ID=your-account-id
STORAGE_R2_KEY=your-access-key
STORAGE_R2_SECRET=your-secret-key
STORAGE_R2_BUCKET=your-bucket
STORAGE_R2_CDN=https://pub-xxx.r2.dev

File Types & Validation

Base Framework automatically validates file types and sizes based on the field type in your models.

File Type Specifications

TypeMax SizeExtensions
Image5MB.jpg, .jpeg, .png, .gif, .webp
File50MB.pdf, .doc, .docx, .xls, .xlsx, .ppt
Attachment10MB.zip, .rar, .7z, .tar + all file types

Custom Types

You can define custom file types with specific extensions and size limits in your models.

Usage Examples

Model Definition

go
package models

import "base/core/storage"

type Product struct {
    ID          uint                 `gorm:"primaryKey"`
    Name        string               `gorm:"not null"`
    Description string               `gorm:"type:text"`
    Price       float64              `gorm:"not null"`
    
    // Single image field
    Thumbnail   storage.Attachment   `gorm:"foreignKey:ModelId;references:Id" field_type:"image"`
    
    // Multiple images
    Gallery     []storage.Attachment `gorm:"foreignKey:ModelId;references:Id" field_type:"image" multiple:"true"`
    
    // File attachments
    Manual      storage.Attachment   `gorm:"foreignKey:ModelId;references:Id"`
    Downloads   []storage.Attachment `gorm:"foreignKey:ModelId;references:Id" multiple:"true"`
}

File Upload in Service

go
func (s *ProductService) Create(data CreateProductRequest) (*Product, error) {
    product := &Product{
        Name:        data.Name,
        Description: data.Description,
        Price:       data.Price,
    }
    
    // Handle thumbnail upload
    if data.Thumbnail != nil {
        attachment, err := s.Storage.Upload(data.Thumbnail, "thumbnail", product)
        if err != nil {
            return nil, err
        }
        product.Thumbnail = *attachment
    }
    
    // Handle multiple gallery images
    if len(data.Gallery) > 0 {
        for _, file := range data.Gallery {
            attachment, err := s.Storage.Upload(file, "gallery", product)
            if err != nil {
                continue // or handle error
            }
            product.Gallery = append(product.Gallery, *attachment)
        }
    }
    
    // Save product with attachments
    if err := s.DB.Create(product).Error; err != nil {
        return nil, err
    }
    
    // Emit upload event
    s.Emitter.Emit("product.uploaded", product)
    
    return product, nil
}

File Deletion

go
// Delete single file
if product.Thumbnail.Path != "" {
    err := s.Storage.Delete(product.Thumbnail.Path)
    if err != nil {
        s.Logger.Error("Failed to delete thumbnail", "error", err)
    }
}

// Delete multiple files
for _, attachment := range product.Gallery {
    if err := s.Storage.Delete(attachment.Path); err != nil {
        s.Logger.Error("Failed to delete gallery image", "error", err)
    }
}

// Clear from database
product.Thumbnail = storage.Attachment{}
product.Gallery = []storage.Attachment{}
s.DB.Save(product)

API Response

JSON Response with Attachments

GET /api/products/1

json
{
    "id": 1,
    "name": "Premium Laptop",
    "description": "High-performance laptop for professionals",
    "price": 1299.99,
    "thumbnail": {
        "id": 1,
        "filename": "laptop-thumbnail.jpg",
        "path": "products/thumbnails/laptop-thumbnail.jpg",
        "size": 245632,
        "url": "https://cdn.example.com/products/thumbnails/laptop-thumbnail.jpg",
        "created_at": "2024-01-15T10:30:00Z"
    },
    "gallery": [
        {
            "id": 2,
            "filename": "laptop-angle1.jpg",
            "url": "https://cdn.example.com/products/gallery/laptop-angle1.jpg"
        },
        {
            "id": 3,
            "filename": "laptop-angle2.jpg",
            "url": "https://cdn.example.com/products/gallery/laptop-angle2.jpg"
        }
    ],
    "manual": {
        "id": 4,
        "filename": "user-manual.pdf",
        "size": 2458976,
        "url": "https://cdn.example.com/products/manuals/user-manual.pdf"
    }
}

Storage Events

The storage system emits events that you can listen to for additional processing:

Upload Events

  • {model}.{field}.uploaded - File uploaded
  • {model}.{field}.processing - File processing
  • {model}.{field}.completed - Upload complete

Deletion Events

  • {model}.{field}.deleted - File deleted
  • {model}.{field}.cleanup - Cleanup performed
  • {model}.{field}.error - Error occurred

Best Practices

Do's

  • Validate file types and sizes before upload
  • Use CDN URLs for production environments
  • Clean up orphaned files when records are deleted
  • Use transactions when uploading multiple files
  • Implement virus scanning for user uploads

Don'ts

  • Don't store sensitive files without encryption
  • Don't allow unlimited file sizes
  • Don't trust client-side file validation alone
  • Don't expose internal file paths in APIs
  • Don't forget to set proper CORS headers for CDN

Released under the MIT License.