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: {
default: process.env.DRIVE_DISK ?? "fs",
drivers: {
fs: drivers.fs({ /* ... */ }),
s3: drivers.s3({ /* ... */ }),
},
},
},
// ...
});
default
Specifies the default storage driver to use.
Type: string
export default defineConfig({
server: {
storage: {
default: "fs", // Use fs driver
// ...
},
},
});
Switching with environment variables:
export default defineConfig({
server: {
storage: {
default: process.env.DRIVE_DISK ?? "fs", // Switch based on environment
// ...
},
},
});
.env:
# Development: Local file system
DRIVE_DISK=fs
# Production: AWS S3
DRIVE_DISK=s3
Using environment variables allows you to switch storage without code changes.
drivers
Defines the storage drivers to use.
Type: Record<string, Driver>
import { drivers } from "sonamu/storage";
export default defineConfig({
server: {
storage: {
default: "fs",
drivers: {
fs: drivers.fs({ /* ... */ }), // Local file system
s3: drivers.s3({ /* ... */ }), // AWS S3
},
},
},
});
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: {
default: "fs",
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)
import path from "path";
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";
export default defineConfig({
server: {
storage: {
default: process.env.DRIVE_DISK ?? "fs", // Switch via environment variable
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
Multiple S3 Buckets
When using multiple S3 buckets for different purposes:
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";
export default defineConfig({
server: {
storage: {
default: "s3-public",
drivers: {
// For public files (profile images, etc.)
"s3-public": drivers.s3({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
},
region: "ap-northeast-2",
bucket: "my-app-public",
visibility: "public",
}),
// For private files (documents, receipts, etc.)
"s3-private": drivers.s3({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
},
region: "ap-northeast-2",
bucket: "my-app-private",
visibility: "private",
}),
},
},
},
// ...
});
Usage example:
import { StorageManager } from "sonamu/storage";
// Upload public image
await StorageManager.use("s3-public").put("profile.jpg", buffer);
// Upload private document
await StorageManager.use("s3-private").put("invoice.pdf", buffer);
File Upload Usage
After storage configuration, you can upload files in your API.
import { api } from "sonamu";
import { StorageManager } from "sonamu/storage";
import type { UploadedFile } from "sonamu";
export class FileModel {
@api({ httpMethod: "POST" })
static async upload(file: UploadedFile) {
// Save file
const key = `uploads/${Date.now()}-${file.filename}`;
await StorageManager.put(key, file.buffer);
// Generate URL
const url = await StorageManager.url(key);
return { key, 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: