Bỏ qua

Storage Concepts

Supabase Storage là gì?

Overview

┌─────────────────────────────────────────────────────────────┐
│                    SUPABASE STORAGE                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────┐        │
│  │                    Storage                       │        │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐        │        │
│  │  │ Bucket  │  │ Bucket  │  │ Bucket  │        │        │
│  │  │ avatars │  │  docs   │  │ images  │        │        │
│  │  ├─────────┤  ├─────────┤  ├─────────┤        │        │
│  │  │file1.jpg│  │doc1.pdf │  │img1.png │        │        │
│  │  │file2.jpg│  │doc2.pdf │  │img2.png │        │        │
│  │  └─────────┘  └─────────┘  └─────────┘        │        │
│  └─────────────────────────────────────────────────┘        │
│                                                              │
│  Features:                                                   │
│  - S3-compatible object storage                             │
│  - RLS policies for access control                          │
│  - Image transformations                                    │
│  - CDN integration                                          │
│  - Resumable uploads                                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Core Concepts

Buckets

Bucket = Container for files (như folder level 1)

Types:
├── Public Bucket
│   - Files accessible via public URL
│   - No auth required to download
│   - Good for: logos, public images
└── Private Bucket
    - Files require authentication
    - Signed URLs for access
    - Good for: user uploads, documents

File Paths

storage/
└── bucket-name/
    └── folder/
        └── subfolder/
            └── file.jpg

Full path: bucket-name/folder/subfolder/file.jpg

Create Buckets

Via Dashboard

Supabase Dashboard → Storage → New bucket

Settings:
- Name: avatars
- Public bucket: Yes/No
- File size limit: 50MB (optional)
- Allowed MIME types: image/* (optional)

Via SQL

-- Create public bucket
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);

-- Create private bucket
INSERT INTO storage.buckets (id, name, public, file_size_limit)
VALUES ('documents', 'documents', false, 10485760);  -- 10MB limit

Via Client

const { data, error } = await supabase.storage.createBucket('avatars', {
  public: true,
  fileSizeLimit: 1024 * 1024 * 5, // 5MB
  allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
});

Storage RLS Policies

Policy Structure

-- Storage policies on storage.objects table
CREATE POLICY "policy_name"
ON storage.objects
FOR operation  -- SELECT, INSERT, UPDATE, DELETE
USING (condition)
WITH CHECK (condition);

Common Policies

-- Anyone can view public bucket files
CREATE POLICY "Public read access"
ON storage.objects FOR SELECT
USING (bucket_id = 'public-images');

-- Authenticated users can upload to avatars
CREATE POLICY "Auth users upload avatars"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars'
  AND auth.role() = 'authenticated'
);

-- Users can only access own files
CREATE POLICY "Users access own files"
ON storage.objects FOR ALL
USING (
  bucket_id = 'user-files'
  AND (storage.foldername(name))[1] = auth.uid()::text
);

File Path Patterns

User-specific Folders

// Pattern: bucket/user_id/filename
const userId = user.id;
const filePath = `${userId}/profile.jpg`;

await supabase.storage
  .from('avatars')
  .upload(filePath, file);

// URL: .../avatars/user-uuid/profile.jpg

Project-based Organization

// Pattern: bucket/project_id/filename
const projectId = 'project-uuid';
const filePath = `${projectId}/documents/report.pdf`;

await supabase.storage
  .from('project-files')
  .upload(filePath, file);

RLS for Path-based Access

-- Policy using folder name
CREATE POLICY "Users access own folder"
ON storage.objects FOR ALL
USING (
  bucket_id = 'user-files'
  AND (storage.foldername(name))[1] = auth.uid()::text
)
WITH CHECK (
  bucket_id = 'user-files'
  AND (storage.foldername(name))[1] = auth.uid()::text
);

Storage Functions

Built-in Functions

-- Get folder names from path
storage.foldername('folder/subfolder/file.jpg')
-- Returns: ['folder', 'subfolder']

-- Get filename
storage.filename('folder/subfolder/file.jpg')
-- Returns: 'file.jpg'

-- Get file extension
storage.extension('folder/file.jpg')
-- Returns: 'jpg'

Usage in Policies

-- Only allow image files
CREATE POLICY "Only images allowed"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'images'
  AND storage.extension(name) IN ('jpg', 'jpeg', 'png', 'gif', 'webp')
);

-- Limit to specific folder depth
CREATE POLICY "Max 2 folder depth"
ON storage.objects FOR INSERT
WITH CHECK (
  array_length(storage.foldername(name), 1) <= 2
);

Public vs Private Access

Public Bucket

// Public bucket - direct URL access
const { data } = supabase.storage
  .from('public-images')
  .getPublicUrl('image.jpg');

// data.publicUrl = https://xxx.supabase.co/storage/v1/object/public/public-images/image.jpg
// Anyone can access this URL

Private Bucket

// Private bucket - signed URL required
const { data, error } = await supabase.storage
  .from('private-docs')
  .createSignedUrl('document.pdf', 3600); // 1 hour expiry

// data.signedUrl = https://xxx.supabase.co/storage/v1/object/sign/private-docs/document.pdf?token=xxx
// URL expires after specified time

Storage Limits

Default Limits

┌─────────────────────────────────────────────────────────────┐
│                    STORAGE LIMITS                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Free Tier:                                                  │
│  - 1 GB storage                                             │
│  - 50 MB file size limit                                    │
│  - 2 GB bandwidth/month                                     │
│                                                              │
│  Pro Tier:                                                   │
│  - 100 GB included                                          │
│  - 5 GB file size limit                                     │
│  - 250 GB bandwidth/month                                   │
│  - $0.021/GB additional storage                             │
│                                                              │
│  Custom limits per bucket:                                   │
│  - file_size_limit                                          │
│  - allowed_mime_types                                       │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Best Practices

Naming Conventions

// ✅ Good: Descriptive, organized
'avatars'           // User profile images
'project-files'     // Project attachments
'public-assets'     // Public static files

// File paths
`${userId}/profile.jpg`
`${projectId}/attachments/${fileId}-${originalName}`

// ❌ Bad: Generic, unorganized
'files'
'uploads'

Security Checklist

✅ Enable RLS on storage.objects
✅ Create specific policies per bucket
✅ Validate file types
✅ Limit file sizes
✅ Use signed URLs for private files
✅ Organize files by user/project ID

Tổng kết

Key Concepts

Concept Description
Bucket Container for files
Public Direct URL access
Private Requires signed URL
RLS Access control via policies

Storage Architecture

storage.buckets     → Bucket definitions
storage.objects     → File metadata (RLS here)
Actual files        → S3-compatible storage

Q&A

  1. Cần lưu trữ loại file nào?
  2. Public hay private buckets?
  3. Đã dùng cloud storage trước đây chưa?