Package Exports
- @ackplus/nest-file-storage
- @ackplus/nest-file-storage/src/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@ackplus/nest-file-storage) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@ackplus/nest-file-storage
A flexible file storage library for NestJS that supports multiple storage providers with simple file mapping capabilities.
Installation
npm install @ackplus/nest-file-storageStorage Providers
This library supports multiple storage providers. You only need to install the dependencies for the storage providers you plan to use.
Local Storage
No additional dependencies required. Works out of the box.
Azure Blob Storage
To use Azure Blob Storage, install the Azure SDK:
npm install @azure/storage-blobAWS S3 Storage
To use AWS S3 Storage, install the AWS SDK:
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presignerUsage
The library supports two approaches: Configuration-based and Class Factory.
Configuration-based Approach (Recommended)
This approach uses lazy loading and only loads the storage provider when needed.
Local Storage
import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
@Module({
imports: [
NestFileStorageModule.forRoot({
storage: FileStorageEnum.LOCAL,
localConfig: {
destination: './uploads'
}
})
]
})
export class AppModule {}Azure Blob Storage
import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
@Module({
imports: [
NestFileStorageModule.forRoot({
storage: FileStorageEnum.AZURE,
azureConfig: {
account: 'your-storage-account',
accountKey: 'your-account-key',
container: 'your-container'
}
})
]
})
export class AppModule {}AWS S3 Storage
import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
@Module({
imports: [
NestFileStorageModule.forRoot({
storage: FileStorageEnum.S3,
s3Config: {
region: 'us-east-1',
bucket: 'your-bucket',
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key'
}
})
]
})
export class AppModule {}Class Factory Approach
This approach allows you to provide your own storage class directly.
import { NestFileStorageModule } from '@ackplus/nest-file-storage';
@Module({
imports: [
NestFileStorageModule.forRoot({
storageFactory: async () => {
// Lazy load the storage class
const { AzureStorage } = await import('@ackplus/nest-file-storage/lib/storage/azure.storage');
return AzureStorage;
},
options: {
account: 'your-storage-account',
accountKey: 'your-account-key',
container: 'your-container'
}
})
]
})
export class AppModule {}Custom Storage Class
You can also provide your own custom storage implementation:
import { NestFileStorageModule, Storage } from '@ackplus/nest-file-storage';
class CustomStorage implements Storage {
// Implement your custom storage logic
async getFile(key: string): Promise<Buffer> { /* ... */ }
async deleteFile(key: string): Promise<void> { /* ... */ }
async putFile(fileContent: Buffer, key: string): Promise<UploadedFile> { /* ... */ }
getUrl(key: string): string { /* ... */ }
async copyFile(oldKey: string, newKey: string): Promise<UploadedFile> { /* ... */ }
}
@Module({
imports: [
NestFileStorageModule.forRoot({
storageFactory: () => CustomStorage,
options: {
// Your custom options
}
})
]
})
export class AppModule {}When to Use Each Approach
Configuration-based Approach:
- ✅ Recommended for most use cases
- ✅ Automatic lazy loading
- ✅ Built-in error handling for missing dependencies
- ✅ Simpler configuration
- ✅ Better for switching between providers
- ✅ Supports async configuration with dependency injection
Class Factory Approach:
- ✅ More control over storage instantiation
- ✅ Custom storage implementations
- ✅ Advanced use cases
- ✅ Still supports lazy loading if implemented correctly
- ⚠️ More complex configuration
- ⚠️ Manual dependency management
Async Configuration (forRootAsync):
- ✅ Full dependency injection support
- ✅
useFactory,useClass, anduseExistingpatterns - ✅ Integration with ConfigService and other providers
- ✅ Environment-based configuration
- ✅ Perfect for complex application setups
Async Configuration (forRootAsync)
For more advanced scenarios, you can use forRootAsync with dependency injection:
Using useFactory
import { NestFileStorageModule, FileStorageEnum } from '@ackplus/nest-file-storage';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule,
NestFileStorageModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
storage: FileStorageEnum.S3,
s3Config: {
region: configService.get('AWS_REGION'),
bucket: configService.get('AWS_BUCKET'),
accessKeyId: configService.get('AWS_ACCESS_KEY_ID'),
secretAccessKey: configService.get('AWS_SECRET_ACCESS_KEY'),
}
}),
inject: [ConfigService],
})
]
})
export class AppModule {}Using useClass
import { Injectable } from '@nestjs/common';
import { NestFileStorageModule, FileStorageOptionsFactory, FileStorageModuleOptions, FileStorageEnum } from '@ackplus/nest-file-storage';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class FileStorageConfigService implements FileStorageOptionsFactory {
constructor(private configService: ConfigService) {}
createFileStorageOptions(): FileStorageModuleOptions {
return {
storage: FileStorageEnum.AZURE,
azureConfig: {
account: this.configService.get('AZURE_STORAGE_ACCOUNT'),
accountKey: this.configService.get('AZURE_STORAGE_KEY'),
container: this.configService.get('AZURE_STORAGE_CONTAINER'),
}
};
}
}
@Module({
imports: [
ConfigModule,
NestFileStorageModule.forRootAsync({
imports: [ConfigModule],
useClass: FileStorageConfigService,
})
]
})
export class AppModule {}Using useExisting
@Module({
imports: [
ConfigModule,
NestFileStorageModule.forRootAsync({
imports: [ConfigModule],
useExisting: FileStorageConfigService,
})
],
providers: [FileStorageConfigService]
})
export class AppModule {}Direct Usage with Storage Factory
You can also use the storage factory directly for more control:
import { StorageFactory, FileStorageEnum } from '@ackplus/nest-file-storage';
// This will lazy load only the Azure storage provider
const azureStorage = await StorageFactory.createStorage(FileStorageEnum.AZURE, {
account: 'your-storage-account',
accountKey: 'your-account-key',
container: 'your-container'
});
// Use the storage instance
const uploadedFile = await azureStorage.putFile(buffer, 'path/to/file.jpg');Flexible File Mapping
The library includes flexible file mapping functionality that allows you to define custom logic for how uploaded files are transformed and stored in the request body.
Default Behavior: Without any configuration, uploaded files are automatically mapped to their storage key (path) in the request body.
Default Behavior (No Configuration)
import { FileStorageInterceptor } from '@ackplus/nest-file-storage';
@Controller('upload')
export class UploadController {
@Post('document')
@UseInterceptors(FileStorageInterceptor('document'))
async uploadDocument(@Body() body: any) {
// body.document will contain the file key (storage path)
console.log(body.document); // "documents/2024/1/1234567890-resume.pdf"
return { message: 'Document uploaded', data: body };
}
}Return File URL String (Custom Logic)
@Post('avatar')
@UseInterceptors(FileStorageInterceptor('avatar', {
mapToRequestBody: (file, fieldName, req) => {
// Return just the URL string for direct DB storage
return file.url;
}
}))
async uploadAvatar(@Body() body: any) {
// body.avatar will be a string URL
console.log(body.avatar); // "http://localhost:3000/uploads/images/avatar.jpg"
return { message: 'Avatar uploaded', data: body };
}Return Custom Object with Business Logic
@Post('document')
@UseInterceptors(FileStorageInterceptor('document', {
mapToRequestBody: (file, fieldName, req) => {
// Return custom object with business logic
return {
fileId: `file_${Date.now()}`,
url: file.url,
originalName: file.originalName,
uploadedBy: req.user?.id || 'anonymous',
uploadedAt: new Date().toISOString(),
isPublic: req.body.isPublic === 'true'
};
}
}))
async uploadDocument(@Body() body: any) {
// body.document will be a custom object
console.log(body.document);
return { message: 'Document uploaded', data: body };
}Multiple Files with Custom Logic
@Post('gallery')
@UseInterceptors(FileStorageInterceptor({
type: 'array',
fieldName: 'images',
maxCount: 5
}, {
mapToRequestBody: (files, fieldName, req) => {
// Return array of custom objects
return files.map((file, index) => ({
id: `img_${Date.now()}_${index}`,
url: file.url,
isPrimary: index === 0,
alt: req.body.imageAlts?.[index] || `Image ${index + 1}`
}));
}
}))
async uploadGallery(@Body() body: any) {
// body.images will be an array of custom objects
console.log(body.images);
return { message: 'Gallery uploaded', data: body };
}Key Features:
- Return anything: String, object, array, or custom structure
- Access request data: Use
reqparameter for user info, body data, etc. - Field-specific logic: Use
fieldNameto handle different fields differently - Business logic: Implement any custom transformation you need
For detailed examples, see FILE_KEY_MAPPING.md.
Error Handling
If you try to use a storage provider without installing its required dependencies, you'll get a clear error message:
- Azure Storage:
Azure Storage SDK (@azure/storage-blob) is required when using AzureStorage. Please install it: npm install @azure/storage-blob - S3 Storage:
AWS SDK (@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner) is required when using S3Storage. Please install them: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
How Lazy Loading Works
The library uses dynamic imports to load storage providers only when they're actually needed:
- At build time: Only the storage factory and types are bundled
- At runtime: When you use a storage provider, it's dynamically imported
- Caching: Once loaded, storage instances are cached for reuse
- Error handling: Missing dependencies are caught and provide clear error messages
This means:
- If you only use local storage, Azure/S3 code is never loaded
- If you only use Azure storage, S3 code is never loaded
- Your bundle size stays minimal regardless of how many storage providers are available
Benefits
- Lazy Loading: Storage providers are only loaded when actually used
- Smaller bundle size: Only install the dependencies you need
- Flexible: Switch between storage providers easily
- Clear error messages: Know exactly what to install if a dependency is missing
- TypeScript support: Full type safety for all storage providers
- Caching: Storage instances are cached to avoid repeated initialization
- Zero overhead: Unused storage providers don't affect your application