LogoMkSaaS Docs

Storage

Learn how to set up and use cloud storage for file uploads and media handling

MkSaaS uses Amazon S3 and compatible services like Cloudflare R2 for file storage and media handling, providing a reliable and scalable solution for storing images and other media files.

Setup

To set up storage in MkSaaS, follow these steps to configure the necessary environment variables:

Cloudflare R2

  1. Create a Cloudflare account at cloudflare.com
  2. Create a new R2 bucket:
    • Pick a globally unique bucket name (e.g., your-project-name)
    • Select a region close to your target audience
    • Set other options according to your needs
  3. Allow public access to the bucket:
    • Settings/Public Access/R2.dev subdomain, click Allow access
    • (Optional) Set custom domains for public access to the bucket
    • Save the public access URL as STORAGE_PUBLIC_URL
  4. Create a new API Token:
    • R2/API/Manage API Tokens, click Create User API Token
    • Set permissions to Object Read & Write to the bucket
    • Create the API Token, get the Access Key ID and Secret Access Key
  5. Set the following environment variables:
.env
STORAGE_REGION=your-region-or-auto
STORAGE_BUCKET_NAME=your-bucket-name
STORAGE_ACCESS_KEY_ID=your-access-key
STORAGE_SECRET_ACCESS_KEY=your-secret-key
STORAGE_ENDPOINT=https://xxx.r2.cloudflarestorage.com
STORAGE_PUBLIC_URL=https://pub-xxx.r2.dev
STORAGE_FORCE_PATH_STYLE=false
  1. Update the website config to use R2 as the storage provider:
src/config/website.tsx
export const websiteConfig = {
  // ...other config
  storage: {
    provider: 's3',
  },
  // ...other config
}

Set storage provider to s3 because Cloudflare R2 is S3-compatible.

Amazon S3

  1. Create an AWS account at aws.amazon.com

  2. Create a new S3 bucket:

    • Pick a globally unique bucket name (e.g., your-project-name)
    • Select a region close to your target audience
    • Disable "Block all public access" if you want files to be publicly accessible
    • Set other options according to your needs
  3. Add a bucket policy to allow public access. Go to the Permissions tab and add:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
  1. Create an IAM user with S3 access:

    • Navigate to the IAM service
    • Create a new policy with S3 permissions: GetObject, PutObject, DeleteObject, ListBucket
    • Create a new IAM user with "Programmatic access"
    • Attach the policy you created
    • Save the Access Key ID and Secret Access Key
  2. Add the following environment variables:

.env
STORAGE_REGION=your-region
STORAGE_BUCKET_NAME=your-bucket-name
STORAGE_ACCESS_KEY_ID=your-access-key
STORAGE_SECRET_ACCESS_KEY=your-secret-key
  1. Update the website config to use S3 as the storage provider:
src/config/website.tsx
export const websiteConfig = {
  // ...other config
  storage: {
    provider: 's3',
  },
  // ...other config
}

S3-Compatible Alternatives

MkSaaS is compatible with any S3-compatible storage service, including:

When using these alternatives, make sure to set the correct endpoint in your environment variables.

If you are setting up the environment, now you can go back to the Environment Setup guide and continue. The rest of this guide can be read later.

Environment Setup

Set up environment variables


Storage System Structure

The storage system in MkSaaS is designed with the following components:

index.ts
types.ts
README.md

This modular structure makes it easy to extend the storage system with new providers and functionality.

Core Features

  • Direct file upload and management for server-side operations
  • Browser-side file uploading with size-based optimization
  • Pre-signed URLs for secure, direct-to-storage uploads
  • Support for Amazon S3 and compatible storage services
  • Folder organization for better file management
  • Automatic path generation and file naming
  • Configurable storage regions and bucket settings
  • Support for public and private file storage

Usage

Basic File Operations

MkSaaS provides simple utilities for common file operations:

import { uploadFile, deleteFile, getPresignedUploadUrl } from '@/storage';
 
// Upload a file to storage
const { url, key } = await uploadFile(
  fileBuffer,
  'original-filename.jpg',
  'image/jpeg',
  'uploads/images'
);
 
// Delete a file from storage
await deleteFile(key);
 
// Generate a pre-signed URL for direct upload
const { url, key } = await getPresignedUploadUrl(
  'filename.jpg',
  'image/jpeg',
  'uploads/images'
);

Browser-Side Uploads

For uploading files directly from the browser, use the uploadFileFromBrowser function:

'use client';
 
import { uploadFileFromBrowser } from '@/storage';
 
// In your React component
async function handleFileUpload(event) {
  const file = event.target.files[0];
  
  try {
    // This will automatically use the most appropriate upload method
    // based on the file size
    const { url, key } = await uploadFileFromBrowser(file, 'uploads/images');
    console.log('File uploaded:', url);
  } catch (error) {
    console.error('Upload failed:', error);
  }
}

This function automatically chooses the best upload method:

  • Small files (<10MB): Direct upload through your API endpoint
  • Large files (>=10MB): Pre-signed URL for direct upload to S3

Using with Form Components

Here's an example of using the storage system with a form component:

ImageUploader.tsx
'use client';
 
import { useState } from 'react';
import { uploadFileFromBrowser } from '@/storage';
import { Button } from '@/components/ui/button';
 
export function ImageUploader() {
  const [isUploading, setIsUploading] = useState(false);
  const [imageUrl, setImageUrl] = useState<string | null>(null);
 
  async function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (!file) return;
 
    try {
      setIsUploading(true);
      const { url } = await uploadFileFromBrowser(file, 'profile-images');
      setImageUrl(url);
    } catch (error) {
      console.error('Upload failed:', error);
    } finally {
      setIsUploading(false);
    }
  }
 
  return (
    <div className="space-y-4">
      <div className="flex items-center gap-4">
        <Button
          variant="outline"
          onClick={() => document.getElementById('file-upload')?.click()}
          disabled={isUploading}
        >
          {isUploading ? 'Uploading...' : 'Upload Image'}
        </Button>
        <input
          id="file-upload"
          type="file"
          accept="image/*"
          className="hidden"
          onChange={handleFileChange}
        />
      </div>
      
      {imageUrl && (
        <div className="w-32 h-32 relative">
          <img 
            src={imageUrl} 
            alt="Uploaded image" 
            className="object-cover w-full h-full rounded-md" 
          />
        </div>
      )}
    </div>
  );
}

Customization

Creating a Custom Provider

MkSaaS makes it easy to extend the storage system with new providers:

  1. Create a new file in the src/storage/provider directory

  2. Implement the StorageProvider interface

src/storage/provider/custom-provider.ts
import {
  PresignedUploadUrlParams,
  StorageProvider,
  UploadFileParams,
  UploadFileResult
} from '@/storage/types';
 
export class CustomStorageProvider implements StorageProvider {
  constructor() {
    // Initialize your provider
  }
 
  public getProviderName(): string {
    return 'CustomProvider';
  }
 
  async uploadFile(params: UploadFileParams): Promise<UploadFileResult> {
    // Implementation for uploading a file
    return { url: 'https://example.com/file.jpg', key: 'file.jpg' };
  }
 
  async deleteFile(key: string): Promise<void> {
    // Implementation for deleting a file
  }
 
  async getPresignedUploadUrl(
    params: PresignedUploadUrlParams
  ): Promise<UploadFileResult> {
    // Implementation for generating a pre-signed URL
    return { url: 'https://example.com/upload', key: 'file.jpg' };
  }
}
  1. Update the provider selection in index.ts:
src/storage/index.ts
import { CustomStorageProvider } from './provider/custom-provider';
 
export const initializeStorageProvider = (): StorageProvider => {
  if (!storageProvider) {
    if (websiteConfig.storage.provider === 's3') {
      storageProvider = new S3Provider();
    } else if (websiteConfig.storage.provider === 'custom') {
      storageProvider = new CustomStorageProvider();
    } else {
      throw new Error(
        `Unsupported storage provider: ${websiteConfig.storage.provider}`
      );
    }
  }
  return storageProvider;
};

Best Practices

  1. File Organization: Use folders to organize files by type or purpose (e.g., uploads/images, documents/contracts)
  2. File Size Limits: Set reasonable file size limits to prevent abuse
  3. File Type Validation: Validate file types on both client and server sides for security
  4. Signed URLs: Use pre-signed URLs for private files that require authentication
  5. CDN Integration: Consider using a CDN in front of your storage for faster delivery
  6. Backup Strategy: Implement a backup strategy for important files
  7. Access Control: Use restrictive IAM policies for your storage user
  8. Error Handling: Properly handle and log storage errors

Next Steps

Now that you understand how to work with file storage in MkSaaS, explore these related topics:

Table of Contents