I currently have some files stored in AWS S3 and others in Cloudflare R2. That made me realize—it would be really convenient to combine everything into a single API. By doing so, I can take advantage of the free tiers offered by multiple cloud storage providers. As soon as I reach the limit on one provider, I can simply create another account or switch to another service and add it to my unified system.
This approach allows me to maintain a single source of truth for all my file storage and retrieval needs. Behind the scenes, it intelligently manages connections to multiple S3-compatible storage providers. This would significantly simplify how I handle file uploads and access across services.
🎯 Why I'm Building This Package:
I'm building a package that acts as a unified interface for multiple S3-compatible cloud storage APIs. The goal is to:
Simplify File Management: Upload and access files through a single API, regardless of the underlying provider (e.g., AWS S3, Cloudflare R2, Wasabi, Backblaze B2, etc.).
Optimize Cost: Leverage free tiers across various providers by intelligently routing uploads or downloads to the least costly or most available storage bucket.
Seamless Scalability: As storage needs grow, I can add more cloud accounts or services without changing how the API is used.
Abstraction of Complexity: Hide provider-specific quirks and authentication behind a single clean interface.
Failover and Redundancy: If one provider fails or reaches a quota, the system automatically falls back to another available provider.
This package will make it easy to build apps or services that need reliable and affordable cloud storage without locking into a single provider or dealing with vendor-specific APIs.
MultiBucket
A Node.js library for generating presigned URLs for multiple object storage providers (AWS S3, Cloudflare R2) with automatic load balancing.
Features
- Support for multiple storage providers (AWS S3 and Cloudflare R2)
- Automatic load balancing between providers using various strategies
- Live configuration updates from file or remote URL
- Rate limiting and error handling
- RESTful API endpoints for generating presigned URLs
- Monitoring and statistics
Installation
npm install multibucket
Dependencies
This library requires the following dependencies:
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner express axios chokidar
Basic Usage
const MultiBucket = require('multibucket');
// Create an instance with initial providers
const storagePresigner = new MultiBucket({
providers: [
{
id: 's3-main',
type: 's3',
bucket: 'my-main-bucket',
region: 'us-east-1',
accessKeyId: 'YOUR_AWS_ACCESS_KEY',
secretAccessKey: 'YOUR_AWS_SECRET_KEY',
weight: 3,
rateLimit: 100
},
{
id: 'r2-cloudflare',
type: 'r2',
bucket: 'my-r2-bucket',
endpoint: 'https://account-id.r2.cloudflarestorage.com',
accessKeyId: 'YOUR_R2_ACCESS_KEY',
secretAccessKey: 'YOUR_R2_SECRET_KEY',
publicUrlBase: 'https://cdn.example.com'
}
],
loadBalanceStrategy: 'round-robin',
defaultExpiry: 3600
});
// Start the API server
storagePresigner.createServer(3000);
Configuration Options
Constructor Options
-
providers
: Array of storage provider configurations -
configSource
: Path or URL to a config file (optional) -
loadBalanceStrategy
: Strategy for load balancing (default: 'round-robin') -
defaultExpiry
: Default expiry time for presigned URLs in seconds (default: 3600)
Provider Configuration
Each provider object requires the following properties:
Common Properties:
-
id
: Unique identifier for the provider -
type
: Provider type ('s3' or 'r2') -
bucket
: Bucket name -
accessKeyId
: Access key ID -
secretAccessKey
: Secret access key -
weight
(optional): Weight for weighted-random load balancing -
rateLimit
(optional): Maximum requests per second -
publicUrlBase
(optional): Base URL for public access
S3-specific Properties:
-
region
: AWS region -
endpoint
(optional): Custom endpoint for S3-compatible services -
forcePathStyle
(optional): Use path-style addressing
R2-specific Properties:
-
endpoint
: R2 endpoint URL
Load Balancing Strategies
-
round-robin
: Cycle through providers sequentially -
least-used
: Select the provider with the fewest requests -
least-errors
: Select the provider with the lowest error rate -
weighted-random
: Select providers randomly based on their weight
External Configuration
You can provide a path to a JSON file or a URL in the configSource
option:
const storagePresigner = new MultiBucket({
configSource: './storage-config.json'
});
The library will watch for changes to the file or poll the URL to update the configuration dynamically.
API Endpoints
When you start the server with createServer()
, the following endpoints are available:
Generate Upload URL
POST /generate-upload-url
Request body:
{
"filename": "example.jpg",
"contentType": "image/jpeg",
"path": "uploads/images",
"expiry": 1800,
"providerId": "s3-main"
}
Response:
{
"uploadUrl": "https://my-main-bucket.s3.us-east-1.amazonaws.com/uploads/images/uuid-example.jpg?...",
"publicUrl": "https://my-main-bucket.s3.us-east-1.amazonaws.com/uploads/images/uuid-example.jpg",
"key": "uploads/images/uuid-example.jpg",
"bucket": "my-main-bucket",
"provider": "s3-main",
"expires": "2023-06-01T12:30:00.000Z"
}
Generate Read URL
POST /generate-read-url
Request body:
{
"key": "uploads/images/uuid-example.jpg",
"providerId": "s3-main",
"expiry": 3600
}
Response:
{
"readUrl": "https://my-main-bucket.s3.us-east-1.amazonaws.com/uploads/images/uuid-example.jpg?...",
"key": "uploads/images/uuid-example.jpg",
"bucket": "my-main-bucket",
"provider": "s3-main",
"expires": "2023-06-01T13:00:00.000Z"
}
Get Stats
GET /stats
Response:
{
"providerCount": 2,
"totalRequests": 150,
"providerStats": [
{
"id": "s3-main",
"type": "s3",
"requestCount": 100,
"errorCount": 2,
"errorRate": "0.0200"
},
{
"id": "r2-cloudflare",
"type": "r2",
"requestCount": 50,
"errorCount": 0,
"errorRate": "0.0000"
}
]
}
Health Check
GET /health
Response:
{
"status": "ok",
"providers": 2,
"timestamp": "2023-06-01T11:00:00.000Z"
}
Programmatic Usage
You can also generate presigned URLs programmatically:
// Generate upload URL
const uploadUrlInfo = await storagePresigner.generateUploadUrl({
filename: 'example.jpg',
contentType: 'image/jpeg',
path: 'uploads/images',
expiry: 1800
});
// Generate read URL
const readUrlInfo = await storagePresigner.generateReadUrl({
key: 'uploads/images/uuid-example.jpg',
providerId: 's3-main',
expiry: 3600
});
License
MIT
"presigned",
"url",
"s3",
"r2",
"cloudflare",
"load-balancing",
"storage",
"upload",
"multi-storage",
"multi-bucket",
"aws-sdk",
"cloudflare-r2",
"express",
"axios",
"chokidar",
"dotenv",
"multi-provider",
"multi-provider-presigner",
"multi-storage-presigner",
"multi-bucket-presigner",
"multi-storage-url",
"multi-bucket-url",
"multi-storage-upload",
"multi-bucket-upload",
"s3-presigner",
"r2-presigner",
"s3-upload",
"r2-upload",
"s3-url",
"r2-url",
"s3-multi-provider",
"r2-multi-provider",
"s3-multi-storage",
"r2-multi-storage"
Top comments (0)