@satyajeetsahoo/storage
v2.1.7
Published
Small local and adapter-based file storage package
Maintainers
Readme
@satyajeetsahoo/storage
A unified storage service supporting Local, Azure Blob, and Cloudflare R2 storage.
Why use this package?
Writing files to the local disk works great in development, but fails immediately when you deploy to serverless environments or multi-node clusters. This package lets you write code against a single storage interface. You can save files locally during development, and swap to Azure Blob or Cloudflare R2 in production without changing your file upload logic.
Features
- Upload anywhere: One method saves to Local Disk, Azure, or Cloudflare.
- Built-in Image Resizing: Resize images on the fly before saving them using sharp.
Installation
npm install @satyajeetsahoo/storageConfiguration & Strategies
Local Storage Strategy (Development)
import { StorageFactory, STORAGE_TYPES } from '@satyajeetsahoo/storage';
export const storage = StorageFactory.createStorage({
type: STORAGE_TYPES.local,
rootDirectory: './uploads'
});Azure Blob Storage Strategy (Production)
import { StorageFactory, STORAGE_TYPES } from '@satyajeetsahoo/storage';
export const storage = StorageFactory.createStorage({
type: STORAGE_TYPES.azure,
connectionString: process.env.AZURE_CONN,
containerName: 'uploads'
});Cloudflare Images/Stream Strategy (Production)
For generating direct upload URLs for videos and images.
import { StorageFactory, STORAGE_TYPES } from '@satyajeetsahoo/storage';
export const streamStorage = StorageFactory.createStorage({
type: STORAGE_TYPES.cloudflare,
accountId: process.env.CF_ACCOUNT_ID,
apiToken: process.env.CF_API_TOKEN
});Cloudflare R2 Strategy (Production)
For saving standard files (documents, PDFs) using S3 protocols.
import { StorageFactory, STORAGE_TYPES } from '@satyajeetsahoo/storage';
export const r2Storage = StorageFactory.createStorage({
type: STORAGE_TYPES.cloudflare,
r2: {
endpoint: process.env.R2_ENDPOINT,
accessKey: process.env.R2_ACCESS_KEY,
secretKey: process.env.R2_SECRET_KEY,
bucket: process.env.R2_BUCKET
}
});Method Compatibility Matrix
Because storage providers are fundamentally different, not all methods are supported by all strategies. Using an unsupported method will throw a NotImplementedError.
| Method | Local / Azure | Cloudflare R2 | Cloudflare Images/Stream |
|--------|---------------|---------------|--------------------------|
| saveFile() | ✅ Yes | ❌ No | ❌ No |
| deleteFile() | ✅ Yes | ❌ No | ❌ No |
| resizeImage() | ✅ Yes | ❌ No | ❌ No |
| getPresignedUploadUrl() | ❌ No | ✅ Yes | ❌ No |
| getPresignedDownloadUrl()| ❌ No | ✅ Yes | ❌ No |
| getImageUploadUrl() | ❌ No | ❌ No | ✅ Yes |
| getVideoUploadUrl() | ❌ No | ❌ No | ✅ Yes |
| getVideoStatus() | ❌ No | ❌ No | ✅ Yes |
| deleteImage() | ❌ No | ❌ No | ✅ Yes |
| deleteVideo() | ❌ No | ❌ No | ✅ Yes |
Usage Examples
Prerequisite Note for Express: In order for
req.filesto be populated with adatabuffer (as seen in the examples below), you must install and use a file-parsing middleware in your Express app, such asexpress-fileuploadormulter(with memory storage).npm install express-fileuploadimport fileUpload from 'express-fileupload'; app.use(fileUpload());
1. Uploading a File
const savedFilePath = await storage.saveFile(
{ data: req.files.avatar.data }, // The raw Buffer
`users/${req.user.id}`, // The target folder
req.files.avatar.name // The target filename
);
console.log(savedFilePath); // e.g., 'users/123/avatar.jpg'2. Resizing an Image
await storage.resizeImage(
`users/${req.user.id}/avatar.jpg`,
`users/${req.user.id}/avatar_thumbnail.jpg`
);3. Deleting a File
await storage.deleteFile(`users/${req.user.id}/avatar.jpg`);4. Cloudflare R2: Generate Presigned Upload URL
If you want to allow clients to upload directly to R2 without routing the file through your Node.js server, generate a presigned upload URL:
const result = await storage.getPresignedUploadUrl({
prefix: 'user_uploads',
id: req.user.id,
fileType: 'IMAGE',
contentType: 'image/jpeg',
fileExtension: 'jpg'
});
// Send this back to the client so they can PUT the file directly to R2
console.log(result.uploadURL);
console.log(result.key); // The R2 path where the file will be saved5. Cloudflare R2: Generate Presigned Download URL
Generate a secure, expiring URL to let clients download private files.
const downloadUrl = await storage.getPresignedDownloadUrl(
'user_uploads/123/2026-09-04/abc.jpg',
'image/jpeg',
true // Forces a download attachment in the browser
);
console.log(downloadUrl);6. Cloudflare Images: Direct Upload URL
Generate a one-time upload URL for Cloudflare Images. This allows clients to upload images directly to Cloudflare without hitting your server.
const imageUpload = await storage.getImageUploadUrl();
console.log(imageUpload.uploadURL); // Send to client to PUT the image
console.log(imageUpload.id); // Store this ID in your database7. Cloudflare Stream: Direct Video Upload URL
Generate a one-time upload URL for Cloudflare Stream.
const videoUpload = await storage.getVideoUploadUrl({
maxDurationSeconds: 3600 // Optional: Restrict video length
});
console.log(videoUpload.uploadURL); // Send to client
console.log(videoUpload.uid); // Store this UID in your database8. Cloudflare Stream: Get Video Status
Check if a video has finished processing and is ready to stream.
const status = await storage.getVideoStatus('video_uid_from_db');
console.log(status.readyToStream); // boolean
console.log(status.playback); // HLS/DASH playback URLs
console.log(status.thumbnail); // URL to the video thumbnail9. Cloudflare Images: Delete an Image
await storage.deleteImage('image_id_from_db');10. Cloudflare Stream: Delete a Video
await storage.deleteVideo('video_uid_from_db');© 2026 Satyajeet Sahoo. All Rights Reserved.
