Sonamu νλ‘μ νΈμμ μΈλΆ μλΉμ€ (AWS S3, OpenAI λ±)λ₯Ό μ¬μ©ν λ API ν€λ₯Ό νκ²½λ³μλ‘ κ΄λ¦¬ν©λλ€. μ½λμ νλμ½λ©νμ§ μκ³ μμ νκ² λ³΄κ΄νλ λ°©λ²μ μ€λͺ
ν©λλ€.
API ν€λ μ λ Gitμ 컀λ°νλ©΄ μ λ©λλ€! μ μΆλλ©΄ μ¦μ ν€λ₯Ό 무ν¨ννκ³ μ¬λ°κΈλ°μΌμΈμ.
μ£Όμ API ν€
Sonamuμμ μμ£Ό μ¬μ©νλ μΈλΆ μλΉμ€ API ν€μ
λλ€.
AWS (S3 μ€ν 리μ§)
# AWS μ격 μ¦λͺ
AWS_ACCESS_KEY_ID=your-aws-access-key-id
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key
# S3 μ€μ
S3_REGION=ap-northeast-2
S3_BUCKET=my-project-uploads
sonamu.config.ts:
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";
export default defineConfig({
server: {
storage: {
default: process.env.DRIVE_DISK ?? "fs",
drivers: {
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 ?? "default-bucket",
visibility: "private",
}),
},
},
},
});
# OpenAI API ν€
OPENAI_API_KEY=your-openai-api-key
OPENAI_ORG_ID=your-openai-org-id # Organization ID (μ ν)
μ¬μ© μμ:
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
organization: process.env.OPENAI_ORG_ID,
});
const completion = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: "Hello!" }],
});
μ νμ μΈλΆ μλΉμ€ ν΅ν©
Sonamuλ λ€μν μΈλΆ μλΉμ€λ₯Ό ν΅ν©ν μ μμ΅λλ€. μλλ μμ£Ό μ¬μ©λλ μλΉμ€λ€μ νκ²½λ³μ μμμ
λλ€.
μ΄ μλΉμ€λ€μ Sonamu κΈ°λ³Έ ꡬμ±μ΄ μλλλ€. νλ‘μ νΈμ νμν κ²½μ°μλ§ μΆκ°νμΈμ.
# Stripe κ²°μ
STRIPE_SECRET_KEY=your-stripe-secret-key
STRIPE_PUBLISHABLE_KEY=your-stripe-publishable-key
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
# SendGrid μ΄λ©μΌ SENDGRID_API_KEY=your-sendgrid-api-key
SENDGRID_FROM_EMAIL=noreply@example.com ```
</Tab>
<Tab title="Twilio" icon="phone">
```bash # Twilio SMS TWILIO_ACCOUNT_SID=your-twilio-account-sid
TWILIO_AUTH_TOKEN=your-twilio-auth-token TWILIO_PHONE_NUMBER=+1234567890 ```
</Tab>
<Tab title="Firebase" icon="fire">
```bash
# Firebase
FIREBASE_API_KEY=your-firebase-api-key
FIREBASE_PROJECT_ID=my-project
FIREBASE_APP_ID=your-firebase-app-id
νκ²½λ³ ν€ κ΄λ¦¬
μ λ νλ‘λμ
ν€λ₯Ό κ°λ° νκ²½μ μ¬μ©νμ§ λ§μΈμ! κ° νκ²½λ§λ€ λ³λμ ν€λ₯Ό μ¬μ©ν΄μΌ ν©λλ€.
κ°λ°
μ€ν
μ΄μ§
νλ‘λμ
# ν
μ€νΈμ© ν€ λλ μλλ°μ€ ν€
OPENAI_API_KEY=your-test-openai-key
AWS_ACCESS_KEY_ID=your-test-aws-key-id
AWS_SECRET_ACCESS_KEY=your-test-aws-secret-key
S3_BUCKET=myproject-dev
STRIPE_SECRET_KEY=your-test-stripe-key
νΉμ§:
- ν
μ€νΈ λͺ¨λ ν€ μ¬μ©
- μκΈ λΆκ³Ό μκ±°λ μ΅μν
- μ νλ κΆν
# μ€ν
μ΄μ§ μ μ© ν€
OPENAI_API_KEY=your-staging-openai-key
AWS_ACCESS_KEY_ID=your-staging-aws-key-id
AWS_SECRET_ACCESS_KEY=your-staging-aws-secret-key
S3_BUCKET=myproject-staging
STRIPE_SECRET_KEY=your-test-stripe-key # μ¬μ ν ν
μ€νΈ λͺ¨λ
νΉμ§:
- νλ‘λμ
κ³Ό λΆλ¦¬λ 리μμ€
- μ€μ μκΈ λ°μ κ°λ₯
- νλ‘λμ
κ³Ό λμΌν κΆν
# νλ‘λμ
ν€ (λ§€μ° μ격νκ² κ΄λ¦¬)
OPENAI_API_KEY=your-production-openai-key
AWS_ACCESS_KEY_ID=your-production-aws-key-id
AWS_SECRET_ACCESS_KEY=your-production-aws-secret-key
S3_BUCKET=myproject-production
STRIPE_SECRET_KEY=your-live-stripe-key # λΌμ΄λΈ λͺ¨λ
νΉμ§:
- μ΅κ³ μμ€μ 보μ
- μ΅μ κΆν μμΉ μ μ©
- μ£ΌκΈ°μ μΈ λ‘ν
μ΄μ
AWS IAM μ¬μ©μ μμ±
μ¬μ©μ μμ±
- Users β Add users 2. μ¬μ©μ μ΄λ¦:
sonamu-s3-user 3. Access type: Programmatic access μ ν
κΆν μ€μ
Option 1: κΈ°μ‘΄ μ μ±
μ°κ²°AmazonS3FullAccess β (λ무 κ΄λ²μ)
Option 2: 컀μ€ν
μ μ±
(κΆμ₯){
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::myproject-uploads/*",
"arn:aws:s3:::myproject-uploads"
]
}
]
}
νΉμ λ²ν·κ³Ό μμ
λ§ νμ©νμ¬ λ³΄μμ κ°ννμΈμ.
μ‘μΈμ€ ν€ λ°κΈ
μ¬μ©μ μμ± μλ£ ν:
- Access key ID:
your-aws-access-key-id
- Secret access key:
your-aws-secret-access-key
Secret access keyλ ν λ²λ§ νμλ©λλ€! μ¦μ μμ ν κ³³μ μ μ₯νμΈμ.
.env νμΌμ μΆκ°
AWS_ACCESS_KEY_ID=your-aws-access-key-id
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key
OpenAI API ν€ λ°κΈ
API ν€ μμ±
- API keys β Create new secret key 2. μ΄λ¦:
sonamu-production 3. κΆν: All λλ
Restricted (κΆμ₯)
μ¬μ© νλ μ€μ
Settings β Billing β Usage limits μ€μ Hard limit: $100/month (μ΄κ³Ό μ μλ μ°¨λ¨)
Soft limit: $80/month (μλ¦Ό)
μ¬μ© νλλ₯Ό μ€μ νμ§ μμΌλ©΄ μμμΉ λͺ»ν μκΈμ΄ μ²κ΅¬λ μ μμ΅λλ€!
ν€ μ μ₯
OPENAI_API_KEY=your-openai-api-key
λ°κΈ μ¦μ μμ ν κ³³μ μ μ₯ (λ€μ νμΈ λΆκ°)
보μ λͺ¨λ² μ¬λ‘
μ κΈ°μ μΈ ν€ κ΅μ²΄
κΆμ₯ μ£ΌκΈ°:
- νλ‘λμ
: 3κ°μλ§λ€
- μ€ν
μ΄μ§: 6κ°μλ§λ€
- κ°λ°: 1λ
λ§λ€ λλ νμ μ
λ‘ν
μ΄μ
μ μ°¨
μ ν€ μμ±
κΈ°μ‘΄ ν€λ₯Ό μ μ§ν μ± μ ν€λ₯Ό μμ±ν©λλ€.
μ ν€ λ°°ν¬
# μ ν€λ₯Ό νκ²½λ³μμ μΆκ°
AWS_ACCESS_KEY_ID=your-new-aws-key-id
AWS_SECRET_ACCESS_KEY=your-new-aws-secret-key
# μ ν리μΌμ΄μ
μ¬μμ
λͺ¨λν°λ§
24μκ° λμ μ€λ₯ μμ΄ μλνλμ§ νμΈ
μ΄μ ν€ μμ
λ¬Έμ μμΌλ©΄ μ΄μ ν€ λΉνμ±ν/μμ
ν€ λ‘ν
μ΄μ
μ λ€μ΄νμ μμ΄ μ§ννλ €λ©΄ μ ν€μ μ΄μ ν€λ₯Ό λμμ μ μ§νλ λΈλ£¨-κ·Έλ¦° λ°©μμ μ¬μ©νμΈμ.
IAM μ μ±
μ΅μν
// β λμ μ: λͺ¨λ κΆν
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
// β
μ’μ μ: νμν κΆνλ§
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-bucket/uploads/*"
}
μ½κΈ° μ μ© ν€ λΆλ¦¬
# μ°κΈ° κΆν (μ ν리μΌμ΄μ
)
AWS_WRITE_ACCESS_KEY_ID=your-write-key-id
AWS_WRITE_SECRET_ACCESS_KEY=your-write-secret-key
# μ½κΈ° κΆν (λΆμ/λ°±μ
)
AWS_READ_ACCESS_KEY_ID=your-read-key-id
AWS_READ_SECRET_ACCESS_KEY=your-read-secret-key
κ°λ° νκ²½
# dotenv-vault μ¬μ©
npm install -g dotenv-vault
# μνΈν
npx dotenv-vault encrypt
# 볡νΈν (νμ)
DOTENV_KEY=your-dotenv-vault-key npx dotenv-vault decrypt
νλ‘λμ
νκ²½
AWS Systems Manager Parameter Store:import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
const client = new SSMClient({ region: "ap-northeast-2" });
async function getSecret(name: string) {
const command = new GetParameterCommand({
Name: name,
WithDecryption: true,
});
const response = await client.send(command);
return response.Parameter?.Value;
}
// μ¬μ©
const apiKey = await getSecret("/myproject/prod/openai-api-key");
HashiCorp Vault:import vault from "node-vault";
const client = vault({
endpoint: "https://vault.example.com:8200",
token: process.env.VAULT_TOKEN,
});
const { data } = await client.read("secret/data/myproject/prod");
const apiKey = data.data.OPENAI_API_KEY;
ν€ μ¬μ© λͺ¨λν°λ§
AWS CloudWatch
// AWS SDKλ‘ μ¬μ©λ λͺ¨λν°λ§
import { CloudWatchClient, GetMetricStatisticsCommand } from "@aws-sdk/client-cloudwatch";
const client = new CloudWatchClient({ region: "ap-northeast-2" });
const stats = await client.send(
new GetMetricStatisticsCommand({
Namespace: "AWS/S3",
MetricName: "NumberOfObjects",
Dimensions: [
{
Name: "BucketName",
Value: "my-project-uploads",
},
],
StartTime: new Date(Date.now() - 86400000), // 24μκ° μ
EndTime: new Date(),
Period: 3600, // 1μκ°
Statistics: ["Average"],
}),
);
OpenAI μ¬μ©λ μλ¦Ό
// μ¬μ©λ νμΈ λ° μλ¦Ό
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// μ£ΌκΈ°μ μΌλ‘ μ€ν (cron job)
async function checkUsage() {
const usage = await fetch("https://api.openai.com/v1/usage", {
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
},
}).then((r) => r.json());
const totalCost = usage.total_usage * 0.0001; // μμ μκΈ
if (totalCost > 80) {
// κ²½κ³ μλ¦Ό μ μ‘ (Slack, Email λ±)
console.warn(`β οΈ OpenAI μ¬μ©λ κ²½κ³ : $${totalCost}`);
}
}
ν
μ€νΈ νκ²½ ν€ κ΄λ¦¬
# .env.test
OPENAI_API_KEY=test-mock-key
AWS_ACCESS_KEY_ID=test-key-id
AWS_SECRET_ACCESS_KEY=test-secret-key
ν
μ€νΈμμλ μ€μ APIλ₯Ό νΈμΆνμ§ μλλ‘ Mockμ μ¬μ©νμΈμ. ν
μ€νΈ λΉμ©μ μ κ°νκ³ μλλ₯Ό λμΌ μ
μμ΅λλ€.
λ¬Έμ ν΄κ²°
μ¦μ:Error: The AWS Access Key Id you provided does not exist in our records
μμΈ:
- μλͺ»λ Access Key ID
- ν€κ° μμ λμκ±°λ λΉνμ±νλ¨
- νμ΄ν μ€λ₯ (곡백, μ€λ°κΏ)
ν΄κ²°:# ν€ νμΈ
echo $AWS_ACCESS_KEY_ID
# .env νμΌ νμΈ
cat .env | grep AWS
# IAM μ½μμμ ν€ μν νμΈ
# Active μνμΈμ§ νμΈ
μ¦μ:RateLimitError: Rate limit reached for gpt-4
μμΈ:
- API μμ² νλ μ΄κ³Ό (RPM, TPM)
ν΄κ²°:// μ¬μλ λ‘μ§ κ΅¬ν
import { RateLimiter } from "limiter";
const limiter = new RateLimiter({
tokensPerInterval: 10,
interval: "minute",
});
async function callOpenAI(prompt: string) {
await limiter.removeTokens(1);
try {
return await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
});
} catch (error) {
if (error.status === 429) {
// 1λΆ ν μ¬μλ
await new Promise((resolve) => setTimeout(resolve, 60000));
return callOpenAI(prompt);
}
throw error;
}
}
μ¦μ:Access Denied: You do not have permission to perform this action
μμΈ:
- IAM μ μ±
μ νμν κΆν μμ
- λ²ν· μ μ±
μΌλ‘ μ°¨λ¨λ¨
ν΄κ²°:// IAM μ μ±
μ κΆν μΆκ°
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
# κΆν ν
μ€νΈ
aws s3 ls s3://my-bucket --profile myproject
λ€μ λ¨κ³
.env μ€μ
νκ²½λ³μ κΈ°λ³Έ μ€μ μ νμ΅νμΈμ
λ°μ΄ν°λ² μ΄μ€ μΈμ¦
DB μ°κ²° μ 보λ₯Ό μμ νκ² κ΄λ¦¬νμΈμ
μ€ν λ¦¬μ§ μ€μ
S3 μ€ν 리μ§λ₯Ό μ€μ νμΈμ
μΈμ
μ€μ
μΈμ
λ° λ³΄μ μ€μ μ νμΈνμΈμ