
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@softvence/s3
Advanced tools
A powerful and flexible NestJS module for uploading files to AWS S3 with built-in caching, automatic folder organization, and hash-based deduplication.
✨ Smart File Upload - Upload single or multiple files to AWS S3 with ease
🗂️ Auto Organization - Automatically organizes files into folders based on MIME type (images, videos, audio, documents)
⚡ Built-in Caching - Optional caching layer using node-cache to prevent duplicate uploads
🔒 Hash-based Deduplication - Uses SHA256 hashing to detect and prevent duplicate file uploads
📦 TypeScript Support - Fully typed with TypeScript for better developer experience
🎯 NestJS Integration - Seamlessly integrates with NestJS applications
npm install @softvence/s3
# or
yarn add @softvence/s3
# or
pnpm add @softvence/s3
Import S3Module in your application module and configure it with your AWS credentials:
import { Module } from "@nestjs/common";
import { S3Module } from "@softvence/s3";
@Module({
imports: [
S3Module.forRoot({
region: "us-east-1",
bucket: "my-bucket-name",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
cache: {
isCache: true, // Enable caching
options: {
stdTTL: 86400, // Cache TTL in seconds (default: 1 day)
checkperiod: 120, // Cache check period in seconds
},
},
}),
],
})
export class AppModule {}
Inject S3Service and start uploading files:
import {
Controller,
Post,
UploadedFile,
UploadedFiles,
UseInterceptors,
} from "@nestjs/common";
import { FileInterceptor, FilesInterceptor } from "@nestjs/platform-express";
import { S3Service } from "@softvence/s3";
@Controller("upload")
export class UploadController {
constructor(private readonly s3Service: S3Service) {}
@Post("single")
@UseInterceptors(FileInterceptor("file"))
async uploadSingle(@UploadedFile() file: Express.Multer.File) {
const result = await this.s3Service.uploadFile(file);
return result;
}
@Post("multiple")
@UseInterceptors(FilesInterceptor("files", 10))
async uploadMultiple(@UploadedFiles() files: Express.Multer.File[]) {
const results = await this.s3Service.uploadFiles(files);
return results;
}
}
| Option | Type | Required | Description |
|---|---|---|---|
region | string | ✅ | AWS region (e.g., 'us-east-1') |
bucket | string | ✅ | S3 bucket name |
accessKeyId | string | ✅ | AWS access key ID |
secretAccessKey | string | ✅ | AWS secret access key |
endpoint | string | ❌ | Custom S3 endpoint (for S3-compatible services) |
cache | S3CacheOptions | ❌ | Cache configuration |
| Option | Type | Default | Description |
|---|---|---|---|
isCache | boolean | false | Enable/disable caching |
options.stdTTL | number | 86400 | Cache TTL in seconds (1 day) |
options.checkperiod | number | 120 | Cache check period in seconds |
| Option | Type | Default | Description |
|---|---|---|---|
maxFileSize | number | 20971520 | Maximum file size in bytes (20 MB) |
maxFiles | number | 20 | Maximum number of files to upload |
actualFileName | string | "" | Custom file name (optional) |
fileHash | string | "" | Custom file hash (optional) |
import { Injectable } from "@nestjs/common";
import { S3Service } from "@softvence/s3";
@Injectable()
export class FileService {
constructor(private readonly s3Service: S3Service) {}
async uploadProfilePicture(file: Express.Multer.File) {
const result = await this.s3Service.uploadFile(file);
console.log("File uploaded:", result.url);
console.log("Bucket:", result.bucket);
console.log("File size:", result.size);
console.log("MIME type:", result.mimeType);
return result;
}
}
async uploadWithMetadata(file: Express.Multer.File) {
const result = await this.s3Service.uploadFile(file, {
maxFileSize: 10 * 1024 * 1024, // 10 MB limit
actualFileName: 'custom-name',
});
return result;
}
async uploadGallery(files: Express.Multer.File[]) {
const results = await this.s3Service.uploadFiles(files, {
maxFiles: 5, // Limit to 5 files
maxFileSize: 5 * 1024 * 1024, // 5 MB per file
});
return results.map(r => ({
url: r.url,
originalName: r.originalName,
cached: r.cached,
}));
}
import {
Controller,
Post,
UploadedFile,
UploadedFiles,
UseInterceptors,
BadRequestException,
} from "@nestjs/common";
import { FileInterceptor, FilesInterceptor } from "@nestjs/platform-express";
import { S3Service } from "@softvence/s3";
@Controller("files")
export class FilesController {
constructor(private readonly s3Service: S3Service) {}
@Post("avatar")
@UseInterceptors(FileInterceptor("avatar"))
async uploadAvatar(@UploadedFile() file: Express.Multer.File) {
if (!file) {
throw new BadRequestException("No file uploaded");
}
const result = await this.s3Service.uploadFile(file, {
maxFileSize: 2 * 1024 * 1024, // 2 MB
});
return {
message: "Avatar uploaded successfully",
url: result.url,
cached: result.cached,
};
}
@Post("documents")
@UseInterceptors(FilesInterceptor("documents", 10))
async uploadDocuments(@UploadedFiles() files: Express.Multer.File[]) {
const results = await this.s3Service.uploadFiles(files, {
maxFiles: 10,
maxFileSize: 10 * 1024 * 1024, // 10 MB per file
});
return {
message: `${results.length} documents uploaded successfully`,
files: results.map((r) => ({
url: r.url,
name: r.originalName,
size: r.size,
folder: r.folder,
})),
};
}
}
Create a .env file:
AWS_REGION=us-east-1
AWS_BUCKET=my-s3-bucket
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
Configure the module:
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { S3Module } from "@softvence/s3";
@Module({
imports: [
ConfigModule.forRoot(),
S3Module.forRoot({
region: process.env.AWS_REGION!,
bucket: process.env.AWS_BUCKET!,
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
cache: {
isCache: true,
},
}),
],
})
export class AppModule {}
For dynamic configuration using ConfigService:
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { S3Module } from "@softvence/s3";
@Module({
imports: [
ConfigModule.forRoot(),
{
module: S3Module,
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
region: configService.get("AWS_REGION")!,
bucket: configService.get("AWS_BUCKET")!,
accessKeyId: configService.get("AWS_ACCESS_KEY_ID")!,
secretAccessKey: configService.get("AWS_SECRET_ACCESS_KEY")!,
cache: {
isCache: true,
options: {
stdTTL: 86400,
},
},
}),
inject: [ConfigService],
},
],
})
export class AppModule {}
uploadFile(file: Express.Multer.File, metadata?: Metadata): Promise<S3Response>Uploads a single file to S3.
Parameters:
file - Multer file objectmetadata - Optional metadata configurationReturns: Promise resolving to S3Response
Throws: BadRequestException if upload fails
uploadFiles(files: Express.Multer.File[], metadata?: Metadata): Promise<S3Response[]>Uploads multiple files to S3.
Parameters:
files - Array of Multer file objectsmetadata - Optional metadata configurationReturns: Promise resolving to array of S3Response
Throws:
BadRequestException if no files providedBadRequestException if file count exceeds maxFilestype S3Response = {
url: string; // Full S3 URL
bucket: string; // Bucket name
region: string; // AWS region
originalName: string; // Original file name
size: number; // File size in bytes
mimeType: string; // MIME type
extension: string; // File extension
folder: string; // Auto-assigned folder (images/videos/audio/documents)
hash?: string; // SHA256 hash (for cached/small files)
cached?: boolean; // Whether file was returned from cache
cacheKey?: string; // Cache key if cached
uploadedAt: Date; // Upload timestamp
metadata?: Metadata; // User-provided metadata
};
Files are automatically organized into folders based on their MIME type:
| MIME Type Pattern | Folder |
|---|---|
image/* | images/ |
video/* | videos/ |
audio/* | audio/ |
| All others | documents/ |
Example URLs:
https://my-bucket.s3.us-east-1.amazonaws.com/images/abc123.jpghttps://my-bucket.s3.us-east-1.amazonaws.com/videos/xyz789.mp4https://my-bucket.s3.us-east-1.amazonaws.com/documents/doc456.pdfWhen caching is enabled:
maxFileSize) are hashed using SHA256Benefits:
Never hardcode AWS credentials. Always use environment variables or a secure configuration service.
S3Module.forRoot({
region: process.env.AWS_REGION!,
bucket: process.env.AWS_BUCKET!,
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
});
Enable caching to improve performance and reduce costs:
cache: {
isCache: true,
options: {
stdTTL: 86400, // 1 day
},
}
Protect your application from large file uploads:
await this.s3Service.uploadFile(file, {
maxFileSize: 5 * 1024 * 1024, // 5 MB
});
Use NestJS pipes to validate file types before upload:
import { ParseFilePipe, MaxFileSizeValidator, FileTypeValidator } from '@nestjs/common';
@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async upload(
@UploadedFile(
new ParseFilePipe({
validators: [
new MaxFileSizeValidator({ maxSize: 5 * 1024 * 1024 }),
new FileTypeValidator({ fileType: 'image/*' }),
],
}),
)
file: Express.Multer.File,
) {
return this.s3Service.uploadFile(file);
}
Always wrap uploads in try-catch blocks:
try {
const result = await this.s3Service.uploadFile(file);
return { success: true, url: result.url };
} catch (error) {
console.error("Upload failed:", error);
throw new BadRequestException("Failed to upload file");
}
If accessing files from a browser, configure CORS on your S3 bucket:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedOrigins": ["*"],
"ExposeHeaders": []
}
]
Grant minimal required permissions to your AWS user/role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject"],
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
The service throws BadRequestException in the following cases:
maxFiles limitExample:
import { BadRequestException } from "@nestjs/common";
try {
const result = await this.s3Service.uploadFile(file);
} catch (error) {
if (error instanceof BadRequestException) {
console.error("Upload error:", error.message);
}
throw error;
}
All types are exported and can be imported:
import {
S3Service,
S3ModuleOptions,
S3Response,
Metadata,
S3CacheOptions,
} from "@softvence/s3";
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Sabbir Hossain Shuvo
For questions and support, please open an issue on GitHub.
Made with ❤️ by the Softvence Team
FAQs
S3 bucket file uploader for softvence
We found that @softvence/s3 demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.