linode-s3-utils
Version:
Clean, intuitive class-based S3-compatible object storage utilities with named parameters for Linode Object Storage and AWS S3
340 lines (269 loc) • 8.52 kB
Markdown
# linode-s3-utils v1.0
Intuitive class-based S3-compatible object storage utilities with **named parameters** for Linode Object Storage and AWS S3.
## ✨ Features
- **Class-based approach** - Create once, use everywhere
- **Named parameters** - Self-documenting object-based API
- **Environment variable support** - Auto-configuration from env vars
- **Auto content-type detection** - Infers MIME types from file extensions
- **Flexible configuration** - Works with Linode Object Storage and AWS S3
- **Clean error handling** - Consistent success/error response format
## Installation
```bash
npm install linode-s3-utils
```
## Quick Start
```javascript
const S3Utils = require('linode-s3-utils')
// Create an S3 instance with your configuration
const s3 = new S3Utils({
region: 'us-east-1',
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key'
})
// Upload a file - self-documenting and clear!
const result = await s3.uploadFile({
filePath: './myfile.jpg',
key: 'uploads/myfile.jpg',
bucket: 'my-bucket',
contentType: 'image/jpeg',
acl: 'public-read'
})
console.log(result.publicUrl)
// Generate presigned URL for client uploads
const presignedUrl = await s3.getPresignedUploadUrl({
key: 'uploads/photo.jpg',
bucket: 'my-bucket',
contentType: 'image/jpeg',
expiresIn: 300
})
// Delete an object
await s3.deleteObject({
key: 'old-file.jpg',
bucket: 'my-bucket'
})
```
### Environment Variables
Set environment variables and create instance without config:
```bash
export LINODE_BUCKET_REGION=us-east-1
export LINODE_S3_ACCESS_KEY=your-access-key
export LINODE_S3_SECRET_KEY=your-secret-key
```
```javascript
const S3Utils = require('linode-s3-utils')
const s3 = new S3Utils() // Uses environment variables automatically
const result = await s3.uploadData({
data: 'Hello World!',
key: 'files/hello.txt',
bucket: 'my-bucket',
contentType: 'text/plain'
})
```
## API Reference
### Constructor
```javascript
const s3 = new S3Utils(config)
```
**Parameters:**
- `config.region` (string): AWS/Linode region
- `config.accessKeyId` (string): Access key ID
- `config.secretAccessKey` (string): Secret access key
- `config.endpoint` (string, optional): Custom endpoint (auto-generated for Linode)
- `config.forcePathStyle` (boolean, optional): Force path-style addressing (default: true)
### Methods
#### `s3.uploadFile(params)`
Upload a file from the local filesystem.
```javascript
const result = await s3.uploadFile({
filePath: './photo.jpg',
key: 'photos/photo.jpg',
bucket: 'my-bucket',
contentType: 'image/jpeg', // optional, auto-detected
acl: 'public-read', // optional, default: 'public-read'
metadata: { // optional
'uploaded-by': 'user123',
'category': 'profile-pics'
}
})
if (result.success) {
console.log('File uploaded:', result.publicUrl)
}
```
**Parameters:**
- `params.filePath` (string): Local path to the file
- `params.key` (string): Object key (path) in the bucket
- `params.bucket` (string): Bucket name
- `params.contentType` (string, optional): MIME type (auto-detected if not provided)
- `params.acl` (string, optional): Access control list (default: 'public-read')
- `params.metadata` (object, optional): Additional metadata to store with the object
#### `s3.uploadData(params)`
Upload data (Buffer or string) directly.
```javascript
const result = await s3.uploadData({
data: 'Hello World!',
key: 'files/hello.txt',
bucket: 'my-bucket',
contentType: 'text/plain',
acl: 'private', // optional
metadata: { // optional
'source': 'api-generated'
}
})
```
**Parameters:**
- `params.data` (Buffer|string): Data to upload
- `params.key` (string): Object key (path) in the bucket
- `params.bucket` (string): Bucket name
- `params.contentType` (string): MIME type
- `params.acl` (string, optional): Access control list (default: 'public-read')
- `params.metadata` (object, optional): Additional metadata to store with the object
#### `s3.getPresignedUploadUrl(params)`
Generate a presigned URL for client-side uploads.
```javascript
const uploadUrl = await s3.getPresignedUploadUrl({
key: 'uploads/user-photo.jpg',
bucket: 'my-bucket',
contentType: 'image/jpeg',
expiresIn: 300, // 5 minutes
acl: 'public-read'
})
```
**Parameters:**
- `params.key` (string): Object key (path) in the bucket
- `params.bucket` (string): Bucket name
- `params.contentType` (string, optional): MIME type (auto-detected if not provided)
- `params.expiresIn` (number, optional): URL expiration time in seconds (default: 180)
- `params.acl` (string, optional): Access control list (default: 'public-read')
#### `s3.deleteObject(params)`
Delete an object.
```javascript
const result = await s3.deleteObject({
key: 'old-file.jpg',
bucket: 'my-bucket'
})
```
**Parameters:**
- `params.key` (string): Object key to delete
- `params.bucket` (string): Bucket name
#### `s3.getPublicUrl(key, bucket)`
Get public URL for an object.
```javascript
const url = s3.getPublicUrl('photos/photo.jpg', 'my-bucket')
```
## Usage Patterns
### Pattern 1: Single Instance for Entire App
```javascript
// utils/s3.js
const S3Utils = require('linode-s3-utils')
const s3 = new S3Utils({
region: process.env.LINODE_BUCKET_REGION,
accessKeyId: process.env.LINODE_S3_ACCESS_KEY,
secretAccessKey: process.env.LINODE_S3_SECRET_KEY,
})
module.exports = s3
```
```javascript
// anywhere in your app
const s3 = require('./utils/s3')
const result = await s3.uploadFile({
filePath: './file.jpg',
key: 'uploads/file.jpg',
bucket: 'my-bucket'
})
```
### Pattern 2: Application-Specific Manager
```javascript
class AppS3Manager {
constructor() {
this.s3 = new S3Utils({ /* config */ })
this.bucketName = 'my-app-bucket'
}
async uploadUserAvatar(userId, imageBuffer, mimeType) {
return await this.s3.uploadData({
data: imageBuffer,
key: `users/${userId}/avatar.${mimeType.split('/')[1]}`,
bucket: this.bucketName,
contentType: mimeType,
metadata: { 'user-id': userId, 'type': 'avatar' }
})
}
async generateUserUploadUrl(userId, fileName, mimeType) {
return await this.s3.getPresignedUploadUrl({
key: `users/${userId}/uploads/${fileName}`,
bucket: this.bucketName,
contentType: mimeType,
expiresIn: 600
})
}
}
const appS3 = new AppS3Manager()
module.exports = appS3
```
### Pattern 3: Express Route Integration
```javascript
const S3Utils = require('linode-s3-utils')
const s3 = new S3Utils({ /* config */ })
router.post('/upload', async (req, res) => {
const result = await s3.uploadData({
data: req.file.buffer,
key: `uploads/${req.file.originalname}`,
bucket: 'my-bucket',
contentType: req.file.mimetype,
metadata: {
'uploaded-by': req.user.id,
'upload-time': new Date().toISOString()
}
})
if (result.success) {
res.json({ url: result.publicUrl })
} else {
res.status(500).json({ error: result.error })
}
})
```
## Why Named Parameters?
### Comparison: Positional vs Named Parameters
**Traditional Approach (Confusing):**
```javascript
// Hard to remember order, unclear what each parameter does
await uploadFile('./file.jpg', 'uploads/file.jpg', 'my-bucket', 'image/jpeg', 'public-read')
```
**Our Approach (Clear):**
```javascript
// Self-documenting, clear, order-independent
await s3.uploadFile({
filePath: './file.jpg',
key: 'uploads/file.jpg',
bucket: 'my-bucket',
contentType: 'image/jpeg',
acl: 'public-read'
})
```
**Benefits:**
- ✅ **Self-documenting** - parameter names make it clear what each value is
- ✅ **Order independent** - can specify parameters in any order
- ✅ **Optional parameters** - easy to omit optional parameters
- ✅ **IDE support** - better autocomplete and IntelliSense
- ✅ **Less error-prone** - harder to mix up parameter order
- ✅ **Future-proof** - easy to add new parameters without breaking changes
## Error Handling
All methods return objects with a `success` boolean:
```javascript
const result = await s3.uploadFile({
filePath: './file.jpg',
key: 'file.jpg',
bucket: 'bucket'
})
if (result.success) {
console.log('Success:', result.publicUrl)
} else {
console.error('Error:', result.error)
}
```
## Supported File Types
Auto-detection for common extensions:
- Images: jpg, jpeg, png, gif
- Documents: pdf, txt, json, html, css, js
- Media: mp3, mp4, mov, wav
## License
MIT