A Node.js library for generating presigned URLs for multiple object storage providers (AWS S3, Cloudflare R2) with automatic load balancing.
- 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
npm install multibucketThis package includes a minimal index.d.ts so TypeScript projects can import it with type information. Example:
import MultiBucket = require('multibucket');
// or (with `esModuleInterop` enabled in tsconfig):
// import MultiBucket from 'multibucket';
const mb = new MultiBucket({ providers: [] });If your TypeScript project still complains about missing types, you can either enable esModuleInterop in tsconfig.json, or, as a temporary workaround, add a declaration stub in the consuming project like declare module 'multibucket';.
This library requires the following dependencies:
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner express axios chokidarconst 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);providers: Array of storage provider configurationsconfigSource: 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)
Each provider object requires the following properties:
id: Unique identifier for the providertype: Provider type ('s3' or 'r2')bucket: Bucket nameaccessKeyId: Access key IDsecretAccessKey: Secret access keyweight(optional): Weight for weighted-random load balancingrateLimit(optional): Maximum requests per secondpublicUrlBase(optional): Base URL for public access
region: AWS regionendpoint(optional): Custom endpoint for S3-compatible servicesforcePathStyle(optional): Use path-style addressing
endpoint: R2 endpoint URL
round-robin: Cycle through providers sequentiallyleast-used: Select the provider with the fewest requestsleast-errors: Select the provider with the lowest error rateweighted-random: Select providers randomly based on their weight
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.
When you start the server with createServer(), the following endpoints are available:
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"
}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
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"
}
]
}GET /health
Response:
{
"status": "ok",
"providers": 2,
"timestamp": "2023-06-01T11:00:00.000Z"
}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
});