master-project
CHAPTER 29 / 50
읽기 약 2분
FUNCTION
파일 API: S3/Supabase Storage + presigned
핵심 개념
Supabase Storage upload·signed URL·multipart·이미지 변환 — 안전한 파일 업로드/다운로드.
본문
Storage 버킷 설계
uploads/ — 사용자 업로드 (Private + RLS)
public/ — 정적 (Public)
generated/ — AI 생성 (Private)
exports/ — 데이터 내보내기 (Signed URL TTL 1시간)직접 업로드 (Server Action)
// src/app/(app)/files/actions.ts
'use server'
import { createClient } from '@/lib/supabase/server'
export async function uploadFile(formData: FormData) {
const file = formData.get('file') as File
if (!file) return { error: '파일 없음' }
if (file.size > 10 * 1024 * 1024) return { error: '10MB 초과' }
if (!['image/png', 'image/jpeg', 'image/webp'].includes(file.type)) {
return { error: '이미지만 가능' }
}
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { error: 'unauthorized' }
const ext = file.name.split('.').pop()
const path = `${user.id}/${Date.now()}.${ext}`
const { error } = await supabase.storage
.from('uploads')
.upload(path, file, { contentType: file.type })
if (error) return { error: error.message }
// DB에 메타 저장
await supabase.from('files').insert({
user_id: user.id,
path,
size: file.size,
mime_type: file.type,
})
return { success: true, path }
}Presigned Upload (큰 파일·직접 업로드)
// src/app/api/files/presigned/route.ts
// 클라이언트가 Supabase에 직접 업로드 → 서버 부하 X
import { createClient } from '@/lib/supabase/server'
export async function POST(request: Request) {
const { filename, content_type } = await request.json()
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return new Response('unauthorized', { status: 401 })
const path = `${user.id}/${Date.now()}-${filename}`
const { data, error } = await supabase.storage
.from('uploads')
.createSignedUploadUrl(path)
if (error) return new Response(error.message, { status: 500 })
return Response.json({ token: data.token, path })
}// 클라이언트
const { token, path } = await fetch('/api/files/presigned', {
method: 'POST',
body: JSON.stringify({ filename: file.name, content_type: file.type }),
}).then(r => r.json())
await supabase.storage.from('uploads').uploadToSignedUrl(path, token, file)Signed URL (다운로드)
// 1시간 유효
const { data } = await supabase.storage
.from('uploads')
.createSignedUrl(path, 3600)
// data.signedUrl → <img src={...} />이미지 변환 (Image Transformations)
const { data } = supabase.storage
.from('uploads')
.getPublicUrl(path, {
transform: {
width: 400,
height: 300,
resize: 'cover',
quality: 80,
format: 'webp',
},
})파일 삭제
// Server Action
'use server'
export async function deleteFile(path: string) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { error: 'unauthorized' }
// 본인 파일 확인 (path가 userId로 시작)
if (!path.startsWith(`${user.id}/`)) return { error: 'forbidden' }
await supabase.storage.from('uploads').remove([path])
await supabase.from('files').delete().eq('path', path)
return { success: true }
}보안 체크리스트
✓ MIME 타입 화이트리스트
✓ 파일 크기 제한
✓ 확장자 검증 (.exe·.sh 차단)
✓ 사용자별 폴더 (RLS)
✓ Public bucket 최소화
✓ Signed URL TTL 짧게
✓ 파일명 sanitize (../ 제거)
✓ 바이러스 스캔 (큰 서비스)
✓ Rate limit (분당 N개)큰 파일 업로드 (TUS resumable)
// 100MB+ 파일은 chunk 단위 (resumable)
import * as tus from 'tus-js-client'
const upload = new tus.Upload(file, {
endpoint: `${SUPABASE_URL}/storage/v1/upload/resumable`,
retryDelays: [0, 3000, 5000, 10000, 20000],
headers: { authorization: `Bearer ${session.access_token}` },
uploadDataDuringCreation: true,
metadata: { bucketName: 'uploads', objectName: path, contentType: file.type },
chunkSize: 6 * 1024 * 1024,
})
upload.start()다음 챕터
CH.30 "실시간 API: Supabase Realtime + WebSocket".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 마스터 프로젝트의 파일 API 부분을 분석해서 실전 적용 + 개선 우선순위 3가지를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
파일 API 관련 모범 사례·안티패턴 5개를 비교 분석해서 실전 적용를 위한 추천 방안을 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 파일 API 최적화 가능 위치와 리스크를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 1인 개발자 시장의 파일 API 트렌드와 차별화 포인트를 정리해줘.
⭐ 이것만 기억하세요
파일 API: S3/Supabase Storage + presigned는 이 3가지만 확실히 잡으세요
1.Supabase Storage = S3 호환 + RLS + 이미지 변환
2.Presigned URL = 큰 파일 직접 업로드 (서버 부하 0)
3.다음 챕터에서 실시간 API
공유하기
진행도 29 / 50