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:
.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: