메인 콘텐츠로 건너뛰기
Sonamu는 Flydrive 기반으로 여러 스토리지 드라이버를 지원합니다. 로컬 파일 시스템(fs)과 AWS S3를 동일한 인터페이스로 사용할 수 있습니다.

지원 드라이버

fs (File System)

로컬 파일 시스템

s3 (AWS S3)

AWS S3 호환 스토리지

fs 드라이버

로컬 파일 시스템에 파일을 저장합니다.

기본 설정

import { drivers, type SonamuConfig } from "sonamu";

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'fs',
      drivers: {
        fs: drivers.fs({
          location: './uploads',  // 파일 저장 위치
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
      }
    }
  }
};

설정 옵션

파일 저장 위치
fs: drivers.fs({
  location: './uploads',  // 상대 경로
})

fs: drivers.fs({
  location: '/var/app/uploads',  // 절대 경로
})
권장: 프로젝트 루트 기준 상대 경로

실전 예제

개발 환경

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'fs',
      drivers: {
        fs: drivers.fs({
          location: './uploads',
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
      }
    }
  }
};
사용:
await disk.put('avatars/user-1.png', buffer);
// 파일 위치: ./uploads/avatars/user-1.png
// URL: /uploads/avatars/user-1.png

정적 파일 서빙

Fastify에서 정적 파일을 서빙해야 합니다:
// sonamu.config.ts
import path from "path";

export const config: SonamuConfig = {
  server: {
    plugins: {
      static: {
        root: path.join(process.cwd(), 'uploads'),
        prefix: '/uploads/',
      }
    },
    storage: {
      default: 'fs',
      drivers: {
        fs: drivers.fs({
          location: './uploads',
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
      }
    }
  }
};
결과:
http://localhost:3000/uploads/avatars/user-1.png
→ ./uploads/avatars/user-1.png 파일 서빙

s3 드라이버

AWS S3 또는 호환 스토리지에 파일을 저장합니다.

기본 설정

import { drivers, type SonamuConfig } from "sonamu";

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 's3',
      drivers: {
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
          },
          region: process.env.AWS_REGION!,
          bucket: process.env.AWS_BUCKET!,
        }),
      }
    }
  }
};

필수 환경변수

AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=ap-northeast-2
AWS_BUCKET=my-app-uploads

설정 옵션

AWS 자격증명
s3: drivers.s3({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
  // ...
})
획득 방법:
  1. AWS Console → IAM
  2. 사용자 생성
  3. S3 권한 부여 (AmazonS3FullAccess)
  4. Access Key 생성

실전 예제

기본 S3 설정

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 's3',
      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',
        }),
      }
    }
  }
};
사용:
await disk.put('avatars/user-1.png', buffer);
// S3 위치: s3://my-app-uploads/avatars/user-1.png
// URL: https://my-app-uploads.s3.ap-northeast-2.amazonaws.com/avatars/user-1.png

CloudFront CDN 연동

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 's3',
      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',
          urlBuilder: {
            generateURL: (key) => 
              `https://${process.env.CLOUDFRONT_DOMAIN}/${key}`,
          }
        }),
      }
    }
  }
};
CLOUDFRONT_DOMAIN=d111111abcdef8.cloudfront.net
장점:
  • 빠른 전송 속도 (엣지 로케이션)
  • 대역폭 비용 절감
  • 캐싱

퍼블릭 버킷 설정

S3 버킷을 퍼블릭으로 설정하려면:
// S3 버킷 정책 (AWS Console → S3 → 버킷 → 권한 → 버킷 정책)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-app-uploads/*"
    }
  ]
}
주의: 민감한 데이터는 private 버킷 사용

S3 호환 스토리지

Cloudflare R2

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'r2',
      drivers: {
        r2: drivers.s3({
          credentials: {
            accessKeyId: process.env.R2_ACCESS_KEY_ID!,
            secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
          },
          endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
          region: 'auto',
          bucket: process.env.R2_BUCKET!,
          urlBuilder: {
            generateURL: (key) => 
              `https://${process.env.R2_PUBLIC_DOMAIN}/${key}`,
          }
        }),
      }
    }
  }
};
특징:
  • 무료 egress (전송 비용 없음)
  • S3 API 호환
  • Cloudflare 네트워크

MinIO (Self-hosted)

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'minio',
      drivers: {
        minio: drivers.s3({
          credentials: {
            accessKeyId: process.env.MINIO_ACCESS_KEY!,
            secretAccessKey: process.env.MINIO_SECRET_KEY!,
          },
          endpoint: 'http://localhost:9000',
          region: 'us-east-1',
          bucket: 'uploads',
          forcePathStyle: true,  // MinIO 필수
        }),
      }
    }
  }
};

드라이버 비교

특징fsS3
비용무료 (디스크 공간)유료 (저장 + 전송)
확장성제한적 (서버 디스크)무제한
속도매우 빠름빠름 (네트워크)
백업수동자동 (버전 관리)
CDN별도 설정 필요CloudFront 연동
용도개발, 소규모프로덕션, 대규모

실전 전략

개발/프로덕션 분리

const isDevelopment = process.env.NODE_ENV === 'development';

export const config: SonamuConfig = {
  server: {
    storage: {
      default: isDevelopment ? 'fs' : 's3',
      drivers: {
        // 개발: 로컬
        fs: drivers.fs({
          location: './uploads',
          urlBuilder: {
            generateURL: (key) => `/uploads/${key}`,
          }
        }),
        
        // 프로덕션: S3
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
          },
          region: process.env.AWS_REGION!,
          bucket: process.env.AWS_BUCKET!,
        }),
      }
    }
  }
};

다중 버킷 전략

export const config: SonamuConfig = {
  server: {
    storage: {
      default: 'private',
      drivers: {
        // 개인 문서 (private)
        private: drivers.s3({
          credentials: { /* ... */ },
          region: 'ap-northeast-2',
          bucket: 'my-app-private',
        }),
        
        // 공개 파일 (public)
        public: drivers.s3({
          credentials: { /* ... */ },
          region: 'ap-northeast-2',
          bucket: 'my-app-public',
        }),
        
        // 백업
        backup: drivers.s3({
          credentials: { /* ... */ },
          region: 'ap-northeast-2',
          bucket: 'my-app-backup',
        }),
      }
    }
  }
};
사용:
// 프로필 사진 → public
await file.saveToDisk('avatars/user-1.png', 'public');

// 개인 문서 → private (기본)
await file.saveToDisk('documents/user-1/doc.pdf');

// 백업 → backup
await Sonamu.storage.use('backup').put('backup.json', data);

주의사항

드라이버 설정 시 주의사항:
  1. 환경변수 관리: 절대 하드코딩 금지
    // ❌ 하드코딩
    credentials: {
      accessKeyId: 'AKIA...',
      secretAccessKey: '...',
    }
    
    // ✅ 환경변수
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    }
    
  2. fs urlBuilder 필수: fs 드라이버는 반드시 urlBuilder 필요
    // ❌ urlBuilder 없음
    fs: drivers.fs({
      location: './uploads',
    })
    
    // ✅ urlBuilder 설정
    fs: drivers.fs({
      location: './uploads',
      urlBuilder: {
        generateURL: (key) => `/uploads/${key}`,
      }
    })
    
  3. S3 권한 확인: IAM 사용자에게 S3 권한 부여
    // 최소 권한
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:PutObject",
            "s3:GetObject",
            "s3:DeleteObject"
          ],
          "Resource": "arn:aws:s3:::my-bucket/*"
        }
      ]
    }
    
  4. 버킷 이름 고유성: S3 버킷 이름은 전역적으로 고유해야 함
    // ❌ 이미 사용 중인 이름
    bucket: 'uploads'  // 에러!
    
    // ✅ 고유한 이름
    bucket: 'my-app-uploads-prod-2024'
    
  5. 리전 일치: bucket과 region이 일치해야 함
    // ❌ 리전 불일치
    region: 'us-east-1',
    bucket: 'my-bucket'  // ap-northeast-2에 생성됨
    
    // ✅ 리전 확인
    region: 'ap-northeast-2',
    bucket: 'my-bucket'  // ap-northeast-2에 생성됨
    

다음 단계