Skip to main content

Migration: v1 → v2

v2 is a redesign around a driver registry: custom storage that works everywhere, per-request / multi-tenant storage, declarative validation, and an injectable service.

:::tip Your v1 config still boots forRoot({ storage, localConfig }) is auto-translated to the v2 shape with a one-time deprecation warning, and FileStorageService.getStorage() still works. Upgrade incrementally. The shims are removed in v3. :::

At a glance

Areav1v2
Module config{ storage: FileStorageEnum.LOCAL, localConfig }{ default: 'local', drivers: { local: localDriver(...) } }
Custom storagestorageFactory (interceptor-broken)drivers: { my: defineDriver(MyDriver, opts) }
Per-request / tenantnot possibletenant: { resolve, driver, cache }
Interceptor backend{ storageType, storageOptions }{ driver: 's3' } or { driver: (req) => '...' }
Validationthrown in fileName() / multerOptions{ validation: { maxSize, allowedMimeTypes, ... } }
Service accessFileStorageService.getStorage() (static)inject FileStorageServicegetDriver()
Result shapingtransformUploadedFileObjectmapToRequestBody
Azure CDNAZURE_CDN_DOMAIN_NAME env varazureDriver({ cdnUrl })
prefixignoredimplemented (per-route + per-tenant)
RemovedtransformUploadedFileObject, multerOptions, fieldname

Module configuration

// v1
NestFileStorageModule.forRoot({
storage: FileStorageEnum.LOCAL,
localConfig: { rootPath: './uploads', baseUrl: 'http://localhost:3000/uploads' },
});

// v2
import { NestFileStorageModule, localDriver } from '@ackplus/nest-file-storage';
NestFileStorageModule.forRoot({
default: 'local',
drivers: { local: localDriver({ rootPath: './uploads', baseUrl: 'http://localhost:3000/uploads' }) },
});

Custom storage

The headline change. In v1, storageFactory only worked through the service and crashed with the interceptor. In v2 a custom driver is just an entry in drivers and works everywhere.

// v1 — remove
NestFileStorageModule.forRoot({ storageFactory: () => MyStorageClass, options: { /* ... */ } });

// v2
import { defineDriver } from '@ackplus/nest-file-storage';
NestFileStorageModule.forRoot({
default: 'gcs',
drivers: { gcs: defineDriver(GcsDriver, { bucket: 'my-bucket' }) },
});

See Custom drivers.

Choosing the backend per route

// v1
FileStorageInterceptor('avatar', { storageType: FileStorageEnum.S3, storageOptions: { bucket, region, /* creds */ } });

// v2 — credentials live in the module's `drivers`; the route just names one
FileStorageInterceptor('avatar', { driver: 's3' });

Validation

// v1 — validating inside fileName
FileStorageInterceptor('image', {
fileName: (file) => {
if (!['image/png', 'image/jpeg'].includes(file.mimetype)) throw new BadRequestException('Invalid type');
return `${Date.now()}-${file.originalname}`;
},
});

// v2 — declarative
FileStorageInterceptor('image', {
validation: { allowedMimeTypes: ['image/png', 'image/jpeg'], maxSize: 5 * 1024 * 1024 },
fileName: (file) => `${Date.now()}-${file.originalname}`,
});

The multerOptions callback is removed: limitsvalidation.maxSize/maxFiles; fileFiltervalidation.fileFilter.

Using the service

// v1
const storage = await FileStorageService.getStorage();
await storage.putFile(buffer, key);

// v2
@Injectable()
class MyService {
constructor(private readonly fileStorage: FileStorageService) {}
save(buffer: Buffer, key: string) { return this.fileStorage.putFile(buffer, key); }
}

FileStorageService.getStorage(name?) still works (deprecated). getStorage(FileStorageEnum.S3) keeps working because the enum value ('s3') is the driver name.

Shaping the result

// v1 — driver-level hook (removed)
localConfig: { transformUploadedFileObject: (file) => ({ key: file.key, url: file.url }) }

// v2 — interceptor option
FileStorageInterceptor('file', { mapToRequestBody: (file) => ({ key: file.key, url: file.url }) });

Other breaking changes

  • UploadedFile.fieldname (lowercase) removed — use fieldName.
  • prefix is now actually applied (ignored in v1) — remove it if you set it expecting a no-op.
  • StorageStorageDriver (a deprecated Storage alias remains). Drivers no longer implement Multer's StorageEngine.
  • S3 signed URLs now honor expiresIn (it was silently ignored in v1).
  • Azure CDN moved from AZURE_CDN_DOMAIN_NAME to azureDriver({ cdnUrl }).

New in v2 worth adopting

  • Multi-tenant storage and tenantFrom resolvers.
  • Registry controls: registerDriver, invalidate, invalidateTenant via fileStorage.getRegistry().
  • Typed validation exceptions.