Sonamu๋ ํ์ผ ์
๋ก๋์ ์ ์ฅ์ ์ํ ํตํฉ ์คํ ๋ฆฌ์ง ์์คํ
์ ์ ๊ณตํฉ๋๋ค. ๋ก์ปฌ ํ์ผ ์์คํ
(fs)๊ณผ AWS S3๋ฅผ ์ง์ํ๋ฉฐ, ํ๊ฒฝ์ ๋ฐ๋ผ ์ฝ๊ฒ ์ ํํ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
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
๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ ์คํ ๋ฆฌ์ง ๋๋ผ์ด๋ฒ๋ฅผ ์ง์ ํฉ๋๋ค.
ํ์
: string
export default defineConfig({
server: {
storage: {
default: "fs", // fs ๋๋ผ์ด๋ฒ ์ฌ์ฉ
// ...
},
},
});
ํ๊ฒฝ ๋ณ์๋ก ์ ํ:
export default defineConfig({
server: {
storage: {
default: process.env.DRIVE_DISK ?? "fs", // ํ๊ฒฝ์ ๋ฐ๋ผ ์ ํ
// ...
},
},
});
.env:
# ๊ฐ๋ฐ: ๋ก์ปฌ ํ์ผ ์์คํ
DRIVE_DISK=fs
# ํ๋ก๋์
: AWS S3
DRIVE_DISK=s3
ํ๊ฒฝ ๋ณ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋ ๋ณ๊ฒฝ ์์ด ์คํ ๋ฆฌ์ง๋ฅผ ์ ํํ ์ ์์ต๋๋ค.
drivers
์ฌ์ฉํ ์คํ ๋ฆฌ์ง ๋๋ผ์ด๋ฒ๋ค์ ์ ์ํฉ๋๋ค.
ํ์
: Record<string, Driver>
import { drivers } from "sonamu/storage";
export default defineConfig({
server: {
storage: {
default: "fs",
drivers: {
fs: drivers.fs({ /* ... */ }), // ๋ก์ปฌ ํ์ผ ์์คํ
s3: drivers.s3({ /* ... */ }), // AWS S3
},
},
},
});
fs ๋๋ผ์ด๋ฒ
๋ก์ปฌ ํ์ผ ์์คํ
์ ํ์ผ์ ์ ์ฅํฉ๋๋ค. ๊ฐ๋ฐ ํ๊ฒฝ์ ์ ํฉํฉ๋๋ค.
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
ํ์ผ์ด ์ ์ฅ๋ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก์
๋๋ค.
ํ์
: string (ํ์)
drivers.fs({
location: path.join(import.meta.dirname, "/../public/uploaded"),
// ...
})
๊ฒฝ๋ก ์์:
visibility
ํ์ผ์ ์ ๊ทผ ๊ถํ์ ์ค์ ํฉ๋๋ค.
ํ์
: "public" | "private"
"public": ๋๊ตฌ๋ URL๋ก ์ ๊ทผ ๊ฐ๋ฅ
"private": ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ ๊ทผ ๊ฐ๋ฅ
drivers.fs({
location: path.join(import.meta.dirname, "/../public/uploaded"),
visibility: "public", // ๊ณต๊ฐ ํ์ผ
// ...
})
urlBuilder
ํ์ผ URL์ ์์ฑํ๋ ํจ์๋ค์ ์ ์ํฉ๋๋ค.
drivers.fs({
// ...
urlBuilder: {
// ์ผ๋ฐ URL ์์ฑ
generateURL(key) {
return `/api/public/uploaded/${key}`;
},
// ์๋ช
๋ URL ์์ฑ (์์ ์ ๊ทผ)
generateSignedURL(key, expiresIn) {
// fs์์๋ ๋ณดํต ์ผ๋ฐ URL๊ณผ ๋์ผ
return `/api/public/uploaded/${key}`;
},
},
})
generateURL: ํ์ผ์ ๊ณต๊ฐ URL์ ์์ฑํฉ๋๋ค.
- key: ํ์ผ์ ๊ณ ์ ํค (์:
"profile-images/user-123.jpg")
- ๋ฐํ: ์ ๊ทผ ๊ฐ๋ฅํ URL
generateSignedURL: ์์๋ก ์ ๊ทผ ๊ฐ๋ฅํ ์๋ช
๋ URL์ ์์ฑํฉ๋๋ค.
- key: ํ์ผ์ ๊ณ ์ ํค
- expiresIn: ๋ง๋ฃ ์๊ฐ (์ด, ์ ํ์ )
- ๋ฐํ: ์์ URL
s3 ๋๋ผ์ด๋ฒ
AWS S3์ ํ์ผ์ ์ ์ฅํฉ๋๋ค. ํ๋ก๋์
ํ๊ฒฝ์ ์ ํฉํฉ๋๋ค.
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
AWS ์ธ์ฆ ์ ๋ณด๋ฅผ ์ค์ ํฉ๋๋ค.
ํ์
: (ํ์)
credentials: {
accessKeyId: string;
secretAccessKey: string;
}
drivers.s3({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
},
// ...
})
AWS ์ธ์ฆ ์ ๋ณด๋ ๋ฐ๋์ ํ๊ฒฝ ๋ณ์๋ก ๊ด๋ฆฌํ์ธ์. ์ฝ๋์ ์ง์ ์์ฑํ์ง ๋ง์ธ์!
.env:
AWS_ACCESS_KEY_ID=your_access_key_here
AWS_SECRET_ACCESS_KEY=your_secret_key_here
S3 ๋ฒํท์ด ์์นํ AWS ๋ฆฌ์ ์
๋๋ค.
ํ์
: string (ํ์)
drivers.s3({
// ...
region: "ap-northeast-2", // ์์ธ ๋ฆฌ์
})
์ฃผ์ ๋ฆฌ์ :
ap-northeast-2 - ์์ธ
us-east-1 - ๋ฒ์ง๋์ ๋ถ๋ถ
us-west-2 - ์ค๋ ๊ณค
eu-west-1 - ์์ผ๋๋
ap-southeast-1 - ์ฑ๊ฐํฌ๋ฅด
ํ์ผ์ ์ ์ฅํ S3 ๋ฒํท ์ด๋ฆ์
๋๋ค.
ํ์
: string (ํ์)
drivers.s3({
// ...
bucket: "my-app-uploads",
})
ํ๊ฒฝ๋ณ๋ก ๋ค๋ฅธ ๋ฒํท ์ฌ์ฉ:
drivers.s3({
// ...
bucket: process.env.S3_BUCKET ?? "my-app-dev",
})
visibility
S3 ๊ฐ์ฒด์ ACL(Access Control List)์ ์ค์ ํฉ๋๋ค.
ํ์
: "public" | "private"
drivers.s3({
// ...
visibility: "private", // ์ธ์ฆ ํ์
})
"public": ํผ๋ธ๋ฆญ ์ฝ๊ธฐ ํ์ฉ (public-read ACL)
"private": ํ๋ผ์ด๋น (private ACL, ๊ธฐ๋ณธ๊ฐ)
์ค์ ์์
๊ฐ๋ฐ ํ๊ฒฝ: fs๋ง ์ฌ์ฉ
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 (ํ๊ฒฝ๋ณ ์ ํ)
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({
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}`;
},
},
}),
// ํ๋ก๋์
: 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
๋ค์ค S3 ๋ฒํท
์ฉ๋๋ณ๋ก ์ฌ๋ฌ S3 ๋ฒํท์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ:
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";
export default defineConfig({
server: {
storage: {
default: "s3-public",
drivers: {
// ๊ณต๊ฐ ํ์ผ์ฉ (ํ๋กํ ์ด๋ฏธ์ง ๋ฑ)
"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",
}),
// ํ๋ผ์ด๋น ํ์ผ์ฉ (๋ฌธ์, ์์์ฆ ๋ฑ)
"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",
}),
},
},
},
// ...
});
์ฌ์ฉ ์์:
import { StorageManager } from "sonamu/storage";
// ๊ณต๊ฐ ์ด๋ฏธ์ง ์
๋ก๋
await StorageManager.use("s3-public").put("profile.jpg", buffer);
// ํ๋ผ์ด๋น ๋ฌธ์ ์
๋ก๋
await StorageManager.use("s3-private").put("invoice.pdf", buffer);
ํ์ผ ์
๋ก๋ ์ฌ์ฉ
์คํ ๋ฆฌ์ง ์ค์ ํ 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) {
// ํ์ผ ์ ์ฅ
const key = `uploads/${Date.now()}-${file.filename}`;
await StorageManager.put(key, file.buffer);
// URL ์์ฑ
const url = await StorageManager.url(key);
return { key, url };
}
}
โ ํ์ผ ์
๋ก๋ ๊ฐ์ด๋
S3 ๋ฒํท ์ค์
S3๋ฅผ ์ฌ์ฉํ๊ธฐ ์ ์ AWS ์ฝ์์์ ๋ฒํท์ ์์ฑํ๊ณ ์ค์ ํด์ผ ํฉ๋๋ค.
1. ๋ฒํท ์์ฑ
# AWS CLI๋ก ๋ฒํท ์์ฑ
aws s3 mb s3://my-app-uploads --region ap-northeast-2
2. CORS ์ค์
ํ๋ก ํธ์๋์์ ์ง์ ์
๋ก๋ํ๋ ค๋ฉด CORS๋ฅผ ์ค์ ํฉ๋๋ค.
S3 ์ฝ์ โ ๋ฒํท โ ๊ถํ โ CORS ๊ตฌ์ฑ:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedOrigins": ["https://myapp.com"],
"ExposeHeaders": ["ETag"]
}
]
3. IAM ๊ถํ
Sonamu๊ฐ S3์ ์ ๊ทผํ๋ ค๋ฉด ์ ์ ํ IAM ๊ถํ์ด ํ์ํฉ๋๋ค.
์ต์ ๊ถํ ์ ์ฑ
:
{
"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/*"
]
}
]
}
์ฃผ์์ฌํญ
1. ํ๊ฒฝ ๋ณ์ ๋ณด์
// โ ๋์ ์: ์ฝ๋์ ์ธ์ฆ ์ ๋ณด ์ง์ ์์ฑ
drivers.s3({
credentials: {
accessKeyId: "AKIAXXXXXXXXXXXXXXXX",
secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCY...",
},
})
// โ
์ข์ ์: ํ๊ฒฝ ๋ณ์ ์ฌ์ฉ
drivers.s3({
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
},
})
2. visibility ์ ํ
// ๊ณต๊ฐ ํ์ผ (ํ๋กํ ์ด๋ฏธ์ง, ์ํ ์ด๋ฏธ์ง ๋ฑ)
visibility: "public"
// ํ๋ผ์ด๋น ํ์ผ (๊ฐ์ธ ๋ฌธ์, ์์์ฆ, ๊ณ์ฝ์ ๋ฑ)
visibility: "private"
3. ํ์ผ ๊ฒฝ๋ก ์ค๊ณ
// โ
์ข์ ์: ์ฒด๊ณ์ ์ธ ๊ฒฝ๋ก ๊ตฌ์กฐ
const key = `users/${userId}/profile/${Date.now()}.jpg`;
const key = `documents/${year}/${month}/${documentId}.pdf`;
// โ ๋์ ์: ํํํ ๊ตฌ์กฐ
const key = `${Date.now()}.jpg`;
๋ค์ ๋จ๊ณ
์คํ ๋ฆฌ์ง ์ค์ ์ ์๋ฃํ๋ค๋ฉด: