Storage Service
The Storage Service provides a unified interface for file uploads, supporting multiple providers like Vercel Blob, Cloudinary, and AWS S3-compatible services (like Cloudflare R2). It abstracts the underlying implementation, allowing you to switch providers with simple configuration changes.
Supported Providers
- Vercel Blob (Default)
- Cloudinary
- AWS S3 / Cloudflare R2
Configuration
Set the STORAGE_PROVIDER environment variable to choose your provider.
Vercel Blob (Default)
Great for Vercel deployments.
# .env
STORAGE_PROVIDER=vercel
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...Cloudinary
Ideal for media optimization and transformation.
# .env
STORAGE_PROVIDER=cloudinary
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
CLOUDINARY_FOLDER=orbitus # Optional, defaults to 'orbitus'AWS S3 / Cloudflare R2
Flexible object storage for any S3-compatible provider.
# .env
STORAGE_PROVIDER=s3
# or 'aws', 'r2'
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1 # or 'auto' for R2
AWS_BUCKET_NAME=your-bucket-name
AWS_ENDPOINT=https://... # Optional for S3, required for R2Usage
Basic Usage
Import storageService to interact with the configured provider.
import { storageService } from '@orbitusdev/core/services/storage';
// Upload a file
const { url } = await storageService.upload('users/avatar.jpg', fileBlob, {
access: 'public'
});
// Delete a file
await storageService.delete(url);API Route Example (App Directory)
Implementation of a file upload endpoint using Next.js Route Handlers.
// app/api/upload/route.ts
import { NextResponse } from 'next/server';
import { storageService } from '@orbitusdev/core/services/storage';
export async function POST(request: Request) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return NextResponse.json(
{ error: 'No file provided' },
{ status: 400 }
);
}
// Generate a unique path/filename if needed
const filename = `uploads/${Date.now()}-${file.name}`;
const { url } = await storageService.upload(filename, file, {
access: 'public'
});
return NextResponse.json({ url });
} catch (error) {
console.error('Upload error:', error);
return NextResponse.json(
{ error: 'Upload failed' },
{ status: 500 }
);
}
}Server Action Example
Using Server Actions for handling form submissions with file uploads directly.
'use server';
import { storageService } from '@orbitusdev/core/services/storage';
export async function uploadAction(formData: FormData) {
const file = formData.get('profileImage') as File;
if (!file || file.size === 0) {
throw new Error('Invalid file');
}
const { url } = await storageService.upload(
`profiles/${file.name}`,
file,
{ access: 'public' }
);
return { success: true, url };
}Interface
The service adheres to the IStorageService interface.
export interface IStorageService {
/**
* Upload a file to the storage provider
* @param path The destination path/filename
* @param file The file or blob content
* @param options Upload options (e.g., public/private access)
*/
upload(
path: string,
file: File | Blob,
options?: { access?: 'public' | 'private' }
): Promise<{ url: string }>;
/**
* Delete a file from the storage provider
* @param url The public URL of the file to delete
*/
delete(url: string): Promise<void>;
}Best Practices
- File Validation: Always validate file type (MIME type) and size before calling upload.
- Unique Filenames: Use timestamps or UUIDs in filenames to prevent overwriting existing files.
- Environment Security: Never expose your secret keys (API_SECRET, AWS_SECRET, etc.) in public variables (
NEXT_PUBLIC_). - Access Control: Use
access: 'private'if the files shouldn’t be publicly accessible (note: not all providers support this simply). - Error Handling: Wrap calls in
try/catchblocks to handle network issues or provider rejections.