JSPM

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

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