
백엔드
File Upload (Multipart, S3 Presigned)파일 업로드 처리
파일 업로드는 클라이언트에서 서버로 파일을 전송하는 메커니즘이다. 직접 서버 업로드, Multipart 스트리밍, S3 프리사인드 URL을 이용한 직접 업로드 방식이 있다.
방식 비교
| 방식 | 설명 | 장점 | 단점 |
|---|---|---|---|
| 직접 업로드 | 서버가 파일 수신 | 제어 용이 | 서버 부하, 느림 |
| Multipart | 청크 분할 전송 | 대용량 지원 | 복잡한 구현 |
| Presigned URL | S3에 직접 업로드 | 서버 부하 없음 | S3 의존 |
Multipart 업로드 (Node.js)
typescript
// multer 미들웨어
import multer from 'multer';
import sharp from 'sharp';
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
fileFilter: (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
cb(null, allowed.includes(file.mimetype));
},
});
app.post('/api/upload', upload.single('file'), async (req, res) => {
const file = req.file!;
// 이미지 최적화
const optimized = await sharp(file.buffer)
.resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 80 })
.toBuffer();
// S3 업로드
const key = `uploads/${Date.now()}-${file.originalname}.webp`;
await s3.putObject({
Bucket: 'my-bucket',
Key: key,
Body: optimized,
ContentType: 'image/webp',
});
res.json({ url: `https://cdn.example.com/${key}` });
});S3 Presigned URL (클라이언트 직접 업로드)
typescript
// 서버: Presigned URL 생성
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({ region: 'ap-northeast-2' });
app.get('/api/upload-url', async (req, res) => {
const { filename, contentType } = req.query;
const key = `uploads/${Date.now()}-${filename}`;
const command = new PutObjectCommand({
Bucket: 'my-bucket',
Key: key,
ContentType: contentType as string,
ContentLength: 10 * 1024 * 1024, // 최대 크기 제한
});
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });
res.json({ uploadUrl, key, publicUrl: `https://cdn.example.com/${key}` });
});
// 클라이언트: Presigned URL로 직접 S3 업로드
async function uploadFile(file: File) {
// 1. 서버에서 Presigned URL 받기
const { uploadUrl, publicUrl } = await fetch(
`/api/upload-url?filename=${file.name}&contentType=${file.type}`
).then(r => r.json());
// 2. S3에 직접 업로드 (서버 거치지 않음)
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});
return publicUrl;
}멀티파트 청크 업로드 (대용량)
typescript
// S3 Multipart Upload (5GB+ 파일)
async function multipartUpload(file: File, bucket: string, key: string) {
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB 청크
// 업로드 시작
const { UploadId } = await s3.createMultipartUpload({ Bucket: bucket, Key: key });
const parts = [];
for (let i = 0; i < Math.ceil(file.size / CHUNK_SIZE); i++) {
const chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
const { ETag } = await s3.uploadPart({
Bucket: bucket, Key: key, UploadId,
PartNumber: i + 1, Body: chunk,
});
parts.push({ ETag, PartNumber: i + 1 });
}
// 업로드 완료
await s3.completeMultipartUpload({ Bucket: bucket, Key: key, UploadId,
MultipartUpload: { Parts: parts } });
}