JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 148
  • Score
    100M100P100Q94559F
  • License MIT

Package Exports

    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-storage

    Storage 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-blob

    AWS S3 Storage

    To use AWS S3 Storage, install the AWS SDK:

    npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

    Usage

    The library supports two approaches: Configuration-based and Class Factory.

    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, and useExisting patterns
    • ✅ 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 req parameter for user info, body data, etc.
    • Field-specific logic: Use fieldName to 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:

    1. At build time: Only the storage factory and types are bundled
    2. At runtime: When you use a storage provider, it's dynamically imported
    3. Caching: Once loaded, storage instances are cached for reuse
    4. 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