OPEN HYPER STEP
← 목록으로 (master-project)
MASTER-PROJECT · 29 / 50
master-project
CHAPTER 29 / 50
읽기 약 2
FUNCTION

파일 API: S3/Supabase Storage + presigned


핵심 개념

Supabase Storage upload·signed URL·multipart·이미지 변환 — 안전한 파일 업로드/다운로드.

본문

Storage 버킷 설계

📋 코드 (4줄)
uploads/        — 사용자 업로드 (Private + RLS)
public/         — 정적 (Public)
generated/      — AI 생성 (Private)
exports/        — 데이터 내보내기 (Signed URL TTL 1시간)

직접 업로드 (Server Action)

TS📋 코드 (35줄)
// 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 (큰 파일·직접 업로드)

TS📋 코드 (18줄)
// 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 })
}
TS📋 코드 (7줄)
// 클라이언트
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 (다운로드)

TS📋 코드 (6줄)
// 1시간 유효
const { data } = await supabase.storage
  .from('uploads')
  .createSignedUrl(path, 3600)

// data.signedUrl → <img src={...} />

이미지 변환 (Image Transformations)

TS📋 코드 (11줄)
const { data } = supabase.storage
  .from('uploads')
  .getPublicUrl(path, {
    transform: {
      width: 400,
      height: 300,
      resize: 'cover',
      quality: 80,
      format: 'webp',
    },
  })

파일 삭제

TS📋 코드 (14줄)
// 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 }
}

보안 체크리스트

📋 코드 (9줄)
✓ MIME 타입 화이트리스트
✓ 파일 크기 제한
✓ 확장자 검증 (.exe·.sh 차단)
✓ 사용자별 폴더 (RLS)
✓ Public bucket 최소화
✓ Signed URL TTL 짧게
✓ 파일명 sanitize (../ 제거)
✓ 바이러스 스캔 (큰 서비스)
✓ Rate limit (분당 N개)

큰 파일 업로드 (TUS resumable)

TS📋 코드 (12줄)
// 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