Skip to main content
Sonamu provides an integrated storage system for file uploads and storage. It supports local file system (fs) and AWS S3, and you can easily switch between them based on the environment.

Basic Structure

import path from "path";
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      drivers: {
        fs: drivers.fs({
          /* ... */
        }),
        s3: drivers.s3({
          /* ... */
        }),
      },
    },
  },
  // ...
});

drivers

Defines the storage drivers to use. Two types are supported: "fs" (local file system) and "s3" (AWS S3). Type: Record<DriverKey, () => DriverContract> (DriverKey = "fs" | "s3")
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      drivers: {
        fs: drivers.fs({
          /* ... */
        }), // Local file system
        s3: drivers.s3({
          /* ... */
        }), // AWS S3
      },
    },
  },
});
When saving files, you explicitly specify which driver to use in the saveToDisk(diskName, key) call. To use different drivers per environment, branch at the code level using environment variables.

fs Driver

Stores files on the local file system. Suitable for development environments.
import path from "path";
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      drivers: {
        fs: drivers.fs({
          location: path.join(import.meta.dirname, "/../public/uploaded"),
          visibility: "public",
          urlBuilder: {
            generateURL(key) {
              return `/api/public/uploaded/${key}`;
            },
            generateSignedURL(key) {
              return `/api/public/uploaded/${key}`;
            },
          },
        }),
      },
    },
  },
});

location

The directory path where files will be stored. Type: string (required)
drivers.fs({
  location: path.join(import.meta.dirname, "/../public/uploaded"),
  // ...
});
Path example:

visibility

Sets the access permissions for files. Type: "public" | "private"
  • "public": Anyone can access via URL
  • "private": Only authenticated users can access
drivers.fs({
  location: path.join(import.meta.dirname, "/../public/uploaded"),
  visibility: "public", // Public files
  // ...
});

urlBuilder

Defines functions to generate file URLs.
drivers.fs({
  // ...
  urlBuilder: {
    // Generate regular URL
    generateURL(key) {
      return `/api/public/uploaded/${key}`;
    },

    // Generate signed URL (temporary access)
    generateSignedURL(key, expiresIn) {
      // In fs, usually same as regular URL
      return `/api/public/uploaded/${key}`;
    },
  },
});
generateURL: Generates the public URL for a file.
  • key: Unique key for the file (e.g., "profile-images/user-123.jpg")
  • Returns: Accessible URL
generateSignedURL: Generates a temporarily accessible signed URL.
  • key: Unique key for the file
  • expiresIn: Expiration time (seconds, optional)
  • Returns: Temporary URL

s3 Driver

Stores files on AWS S3. Suitable for production environments.
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      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",
          visibility: "private",
        }),
      },
    },
  },
});

credentials

Sets AWS authentication credentials. Type: (required)
credentials: {
  accessKeyId: string;
  secretAccessKey: string;
}
drivers.s3({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
  },
  // ...
});
Always manage AWS credentials via environment variables. Never write them directly in code!
.env:
AWS_ACCESS_KEY_ID=your_access_key_here
AWS_SECRET_ACCESS_KEY=your_secret_key_here

region

The AWS region where the S3 bucket is located. Type: string (required)
drivers.s3({
  // ...
  region: "ap-northeast-2", // Seoul region
});
Major regions:
  • ap-northeast-2 - Seoul
  • us-east-1 - N. Virginia
  • us-west-2 - Oregon
  • eu-west-1 - Ireland
  • ap-southeast-1 - Singapore

bucket

The S3 bucket name for storing files. Type: string (required)
drivers.s3({
  // ...
  bucket: "my-app-uploads",
});
Using different buckets per environment:
drivers.s3({
  // ...
  bucket: process.env.S3_BUCKET ?? "my-app-dev",
});

visibility

Sets the ACL (Access Control List) for S3 objects. Type: "public" | "private"
drivers.s3({
  // ...
  visibility: "private", // Authentication required
});
  • "public": Public read allowed (public-read ACL)
  • "private": Private (private ACL, default)

Practical Examples

Development Environment: fs Only

import path from "path";
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      drivers: {
        fs: drivers.fs({
          location: path.join(import.meta.dirname, "/../public/uploaded"),
          visibility: "public",
          urlBuilder: {
            generateURL(key) {
              return `/api/public/uploaded/${key}`;
            },
            generateSignedURL(key) {
              return `/api/public/uploaded/${key}`;
            },
          },
        }),
      },
    },
  },
  // ...
});

fs + S3 (Environment-based Switching)

Register both drivers, then select which driver to use in code based on an environment variable.
import path from "path";
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      drivers: {
        // Development: Local file system
        fs: drivers.fs({
          location: path.join(import.meta.dirname, "/../public/uploaded"),
          visibility: "public",
          urlBuilder: {
            generateURL(key) {
              return `/api/public/uploaded/${key}`;
            },
            generateSignedURL(key) {
              return `/api/public/uploaded/${key}`;
            },
          },
        }),

        // Production: AWS S3
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
          },
          region: process.env.S3_REGION ?? "ap-northeast-2",
          bucket: process.env.S3_BUCKET ?? "my-app-prod",
          visibility: "private",
        }),
      },
    },
  },
  // ...
});
.env.development:
DRIVE_DISK=fs
.env.production:
DRIVE_DISK=s3
S3_REGION=ap-northeast-2
S3_BUCKET=my-app-prod-uploads
AWS_ACCESS_KEY_ID=your_access_key_here
AWS_SECRET_ACCESS_KEY=your_secret_key_here
DriverKey currently supports only "fs" and "s3". Multiple bucket configurations — such as separating public and private files into different buckets — are not supported at this time.

File Upload Usage

After storage configuration, you can upload files using the @upload decorator and BufferedFile.saveToDisk().
import { type DriverKey } from "sonamu/storage";
import { Sonamu } from "sonamu";
import { upload } from "sonamu/decorators";
import { BaseModelClass } from "sonamu";

export class FileModel extends BaseModelClass {
  @upload()
  static async upload() {
    const ctx = Sonamu.getContext();
    const file = ctx.bufferedFiles[0];
    if (!file) throw new Error("No uploaded file found.");

    // Select driver via environment variable
    const diskName: DriverKey = process.env.DRIVE_DISK === "s3" ? "s3" : "fs";
    const url = await file.saveToDisk(diskName, `uploads/${Date.now()}-${file.filename}`);

    return { url };
  }
}
File Upload Guide

S3 Bucket Setup

Before using S3, you need to create and configure a bucket in AWS Console.

1. Create Bucket

# Create bucket with AWS CLI
aws s3 mb s3://my-app-uploads --region ap-northeast-2

2. CORS Configuration

Configure CORS to allow direct uploads from frontend. S3 Console → Bucket → Permissions → CORS configuration:
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedOrigins": ["https://myapp.com"],
    "ExposeHeaders": ["ETag"]
  }
]

3. IAM Permissions

Sonamu needs appropriate IAM permissions to access S3. Minimum permissions policy:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::my-app-uploads", "arn:aws:s3:::my-app-uploads/*"]
    }
  ]
}

Cautions

1. Environment Variable Security

// ❌ Bad: Credentials written directly in code
drivers.s3({
  credentials: {
    accessKeyId: "AKIAXXXXXXXXXXXXXXXX",
    secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCY...",
  },
});

// ✅ Good: Use environment variables
drivers.s3({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
  },
});

2. Choosing visibility

// Public files (profile images, product images, etc.)
visibility: "public";

// Private files (personal documents, receipts, contracts, etc.)
visibility: "private";

3. File Path Design

// ✅ Good: Systematic path structure
const key = `users/${userId}/profile/${Date.now()}.jpg`;
const key = `documents/${year}/${month}/${documentId}.pdf`;

// ❌ Bad: Flat structure
const key = `${Date.now()}.jpg`;

Next Steps

After completing storage configuration: