Skip to main content
Sonamu supports multiple storage drivers based on Flydrive. You can use the local file system (fs) and AWS S3 with the same interface.

Supported Drivers

fs (File System)

Local file system

s3 (AWS S3)

AWS S3 compatible storage

fs Driver

Stores files in the local file system.

Basic Configuration

import { drivers, type SonamuConfig } from "sonamu";

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'fs',
      drivers: {
        fs: drivers.fs({
          location: './uploads',  // File storage location
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
      }
    }
  }
};

Configuration Options

File storage location
fs: drivers.fs({
  location: './uploads',  // Relative path
})

fs: drivers.fs({
  location: '/var/app/uploads',  // Absolute path
})
Recommended: Relative path from project root

Practical Examples

Development Environment

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'fs',
      drivers: {
        fs: drivers.fs({
          location: './uploads',
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
      }
    }
  }
};
Usage:
await disk.put('avatars/user-1.png', buffer);
// File location: ./uploads/avatars/user-1.png
// URL: /uploads/avatars/user-1.png

Static File Serving

You need to serve static files with Fastify:
// sonamu.config.ts
import path from "path";

export const config: SonamuConfig = {
  server: {
    plugins: {
      static: {
        root: path.join(process.cwd(), 'uploads'),
        prefix: '/uploads/',
      }
    },
    storage: {
      default: 'fs',
      drivers: {
        fs: drivers.fs({
          location: './uploads',
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
      }
    }
  }
};
Result:
http://localhost:3000/uploads/avatars/user-1.png
→ Serves ./uploads/avatars/user-1.png file

s3 Driver

Stores files in AWS S3 or compatible storage.

Basic Configuration

import { drivers, type SonamuConfig } from "sonamu";

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 's3',
      drivers: {
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
          },
          region: process.env.AWS_REGION!,
          bucket: process.env.AWS_BUCKET!,
        }),
      }
    }
  }
};

Required Environment Variables

AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=ap-northeast-2
AWS_BUCKET=my-app-uploads

Configuration Options

AWS credentials
s3: drivers.s3({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
  // ...
})
How to obtain:
  1. AWS Console → IAM
  2. Create user
  3. Grant S3 permissions (AmazonS3FullAccess)
  4. Create Access Key

Practical Examples

Basic S3 Configuration

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 's3',
      drivers: {
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
          },
          region: 'ap-northeast-2',
          bucket: 'my-app-uploads',
        }),
      }
    }
  }
};
Usage:
await disk.put('avatars/user-1.png', buffer);
// S3 location: s3://my-app-uploads/avatars/user-1.png
// URL: https://my-app-uploads.s3.ap-northeast-2.amazonaws.com/avatars/user-1.png

CloudFront CDN Integration

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 's3',
      drivers: {
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
          },
          region: 'ap-northeast-2',
          bucket: 'my-app-uploads',
          urlBuilder: {
            generateURL: (key) =>
              `https://${process.env.CLOUDFRONT_DOMAIN}/${key}`,
          }
        }),
      }
    }
  }
};
CLOUDFRONT_DOMAIN=d111111abcdef8.cloudfront.net
Benefits:
  • Fast transfer speed (edge locations)
  • Reduced bandwidth costs
  • Caching

Public Bucket Configuration

To make an S3 bucket public:
// S3 bucket policy (AWS Console → S3 → Bucket → Permissions → Bucket Policy)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-app-uploads/*"
    }
  ]
}
Caution: Use private buckets for sensitive data

S3 Compatible Storage

Cloudflare R2

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'r2',
      drivers: {
        r2: drivers.s3({
          credentials: {
            accessKeyId: process.env.R2_ACCESS_KEY_ID!,
            secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
          },
          endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
          region: 'auto',
          bucket: process.env.R2_BUCKET!,
          urlBuilder: {
            generateURL: (key) =>
              `https://${process.env.R2_PUBLIC_DOMAIN}/${key}`,
          }
        }),
      }
    }
  }
};
Features:
  • Free egress (no transfer costs)
  • S3 API compatible
  • Cloudflare network

MinIO (Self-hosted)

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'minio',
      drivers: {
        minio: drivers.s3({
          credentials: {
            accessKeyId: process.env.MINIO_ACCESS_KEY!,
            secretAccessKey: process.env.MINIO_SECRET_KEY!,
          },
          endpoint: 'http://localhost:9000',
          region: 'us-east-1',
          bucket: 'uploads',
          forcePathStyle: true,  // Required for MinIO
        }),
      }
    }
  }
};

Driver Comparison

FeaturefsS3
CostFree (disk space)Paid (storage + transfer)
ScalabilityLimited (server disk)Unlimited
SpeedVery fastFast (network)
BackupManualAutomatic (versioning)
CDNRequires separate setupCloudFront integration
Use caseDevelopment, small scaleProduction, large scale

Practical Strategies

Development/Production Separation

const isDevelopment = process.env.NODE_ENV === 'development';

export const config: SonamuConfig = {
  server: {
    storage: {
      default: isDevelopment ? 'fs' : 's3',
      drivers: {
        // Development: Local
        fs: drivers.fs({
          location: './uploads',
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),

        // Production: S3
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
          },
          region: process.env.AWS_REGION!,
          bucket: process.env.AWS_BUCKET!,
        }),
      }
    }
  }
};

Multi-Bucket Strategy

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'private',
      drivers: {
        // Private documents
        private: drivers.s3({
          credentials: { /* ... */ },
          region: 'ap-northeast-2',
          bucket: 'my-app-private',
        }),

        // Public files
        public: drivers.s3({
          credentials: { /* ... */ },
          region: 'ap-northeast-2',
          bucket: 'my-app-public',
        }),

        // Backup
        backup: drivers.s3({
          credentials: { /* ... */ },
          region: 'ap-northeast-2',
          bucket: 'my-app-backup',
        }),
      }
    }
  }
};
Usage:
// Profile photos → public
await file.saveToDisk('avatars/user-1.png', 'public');

// Private documents → private (default)
await file.saveToDisk('documents/user-1/doc.pdf');

// Backup → backup
await Sonamu.storage.use('backup').put('backup.json', data);

Cautions

Cautions when configuring drivers:
  1. Environment variable management: Never hardcode
    // ❌ Hardcoded
    credentials: {
      accessKeyId: 'AKIA...',
      secretAccessKey: '...',
    }
    
    // ✅ Environment variables
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    }
    
  2. fs urlBuilder required: fs driver requires urlBuilder
    // ❌ No urlBuilder
    fs: drivers.fs({
      location: './uploads',
    })
    
    // ✅ urlBuilder configured
    fs: drivers.fs({
      location: './uploads',
      urlBuilder: {
        generateURL: (key) => `/uploads/${key}`,
      }
    })
    
  3. S3 permissions check: Grant S3 permissions to IAM user
    // Minimum permissions
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:PutObject",
            "s3:GetObject",
            "s3:DeleteObject"
          ],
          "Resource": "arn:aws:s3:::my-bucket/*"
        }
      ]
    }
    
  4. Bucket name uniqueness: S3 bucket names must be globally unique
    // ❌ Name already in use
    bucket: 'uploads'  // Error!
    
    // ✅ Unique name
    bucket: 'my-app-uploads-prod-2024'
    
  5. Region matching: bucket and region must match
    // ❌ Region mismatch
    region: 'us-east-1',
    bucket: 'my-bucket'  // Created in ap-northeast-2
    
    // ✅ Region verified
    region: 'ap-northeast-2',
    bucket: 'my-bucket'  // Created in ap-northeast-2
    

Next Steps