@rytass/storages-adapter-r2
v0.4.12
Published
Cloudflare R2 storage adapter
Maintainers
Readme
Rytass Utils - Cloudflare R2 Storage Adapter
High-performance storage adapter for Cloudflare R2, providing S3-compatible object storage with global distribution and zero egress fees. Offers the same interface as other storage adapters in the Rytass ecosystem.
Features
- [x] Cloudflare R2 object storage integration
- [x] S3-compatible API interface
- [x] Zero egress fees for data retrieval
- [x] Global edge distribution
- [x] Buffer and Stream file operations
- [x] Pre-signed URL generation
- [x] File existence checking and deletion
- [x] Cost-effective storage solution
- [x] High availability and durability
Installation
npm install @rytass/storages-adapter-r2
# or
yarn add @rytass/storages-adapter-r2Configuration
StorageR2Options
| Property | Type | Required | Description |
| ----------- | -------- | -------- | ------------------------- |
| bucket | string | Yes | R2 bucket name |
| accessKey | string | Yes | R2 Access Key ID |
| secretKey | string | Yes | R2 Secret Access Key |
| accountId | string | Yes | Cloudflare Account ID |
| region | string | No | R2 region (default: auto) |
Usage
Basic Setup
import { StorageR2Service } from '@rytass/storages-adapter-r2';
const storage = new StorageR2Service({
bucket: 'my-r2-bucket',
accessKey: 'your-access-key',
secretKey: 'your-secret-key',
accountId: 'your-cloudflare-account-id',
});File Operations
import { readFileSync } from 'fs';
// Upload file
const fileBuffer = readFileSync('document.pdf');
const result = await storage.write(fileBuffer, {
filename: 'documents/important-doc.pdf',
contentType: 'application/pdf',
});
// Download file as buffer
const downloadedFile = await storage.read('documents/important-doc.pdf', {
format: 'buffer',
});
// Download file as stream (default)
const fileStream = await storage.read('documents/important-doc.pdf');
// Generate public URL
const publicUrl = await storage.url('documents/important-doc.pdf');
// Check if file exists
const exists = await storage.isExists('documents/important-doc.pdf');
// Remove file
await storage.remove('documents/important-doc.pdf');Stream Processing
import { createReadStream } from 'fs';
// Upload large file via stream
const fileStream = createReadStream('large-video.mp4');
const uploadResult = await storage.write(fileStream, {
filename: 'media/videos/large-video.mp4',
contentType: 'video/mp4',
});Integration with File Converter
import { ConverterManager } from '@rytass/file-converter';
import { ImageResizer } from '@rytass/file-converter-adapter-image-resizer';
import { StorageR2Service } from '@rytass/storages-adapter-r2';
const storage = new StorageR2Service({
bucket: 'my-images',
accessKey: process.env.R2_ACCESS_KEY!,
secretKey: process.env.R2_SECRET_KEY!,
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
});
const manager = new ConverterManager([
new ImageResizer({
maxWidth: 1200,
maxHeight: 800,
keepAspectRatio: true,
}),
]);
// Process the image
const processedImage = await manager.convert<Buffer>(imageFile);
// Upload to R2
const result = await storage.write(processedImage, {
filename: 'processed-images/thumbnail.jpg',
contentType: 'image/jpeg',
});Environment Variables
# .env
R2_ACCESS_KEY_ID=your_r2_access_key
R2_SECRET_ACCESS_KEY=your_r2_secret_key
CLOUDFLARE_ACCOUNT_ID=your_account_id
R2_BUCKET_NAME=your-bucket-nameconst storage = new StorageR2Service({
bucket: process.env.R2_BUCKET_NAME!,
accessKey: process.env.R2_ACCESS_KEY_ID!,
secretKey: process.env.R2_SECRET_ACCESS_KEY!,
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
});Error Handling
import { StorageError, ErrorCode } from '@rytass/storages';
try {
const result = await storage.write(fileBuffer, { filename: 'path/to/file.pdf' });
} catch (error) {
if (error instanceof StorageError) {
switch (error.code) {
case ErrorCode.FILE_NOT_FOUND:
console.error('File not found');
break;
case ErrorCode.WRITE_FILE_ERROR:
console.error('Failed to write file');
break;
case ErrorCode.READ_FILE_ERROR:
console.error('Failed to read file');
break;
default:
console.error('Storage error:', error.message);
}
}
}R2 vs S3 Comparison
| Feature | Cloudflare R2 | Amazon S3 | | ------------------- | ------------------ | ------------------- | | Egress Fees | $0 | Charged per GB | | Global Distribution | Built-in | CloudFront required | | API Compatibility | S3-compatible | Native | | Pricing Model | Simple | Complex tiers | | Edge Computing | Cloudflare Workers | Lambda@Edge |
Best Practices
Cost Optimization
- Leverage zero egress fees for frequently accessed content
- Use R2 for serving static assets globally
- Consider R2 for backup storage with frequent retrievals
Performance
- Utilize Cloudflare's global network for faster access
- Implement caching strategies at the edge
- Use appropriate Content-Type headers for better caching
Security
- Use IAM tokens with minimal required permissions
- Enable bucket-level security policies
- Implement proper access logging
Cloudflare R2 Setup
Create R2 Bucket:
# Via Cloudflare Dashboard or API curl -X POST "https://api.cloudflare.com/client/v4/accounts/{account-id}/r2/buckets" \ -H "Authorization: Bearer {api-token}" \ -H "Content-Type: application/json" \ --data '{"name":"my-bucket"}'Generate API Tokens:
- Go to Cloudflare Dashboard → R2 → Manage R2 API tokens
- Create token with appropriate permissions
Configure CORS (if needed):
{ "allowed_origins": ["https://yourdomain.com"], "allowed_methods": ["GET", "PUT", "POST", "DELETE"], "allowed_headers": ["*"], "expose_headers": ["ETag"], "max_age": 3600 }
Migration from S3
// Minimal code changes required due to S3-compatible interface
const storageR2 = new StorageR2Service({
// R2 configuration
});
const storageS3 = new StorageS3Service({
// S3 configuration
});
// Same interface, different storage backend
const operations = [
storage.write(file, { filename: key }),
storage.read(key),
storage.remove(key),
storage.isExists(key),
storage.url(key),
];License
MIT
