Skip to content

azversan/storage-module

@azversan/storage

A flexible, production-ready NestJS storage module with built-in support for Local, Google Cloud Storage (GCS), and AWS S3 (and S3-compatible) drivers.

npm version license


Features

  • 🗂️ Three built-in drivers — Local filesystem, AWS S3 (+ MinIO / Cloudflare R2), Google Cloud Storage
  • Dynamic NestJS module — synchronous and asynchronous registration patterns
  • 🔢 Batch registrationregisterMany() / registerManyAsync() to wire multiple storages in one call
  • 🔑 Injection decorator@InjectStorage('name') for clean constructor injection
  • 🗃️ Registry serviceStorageRegistry to resolve any driver by name at runtime
  • 🔒 Signed URLs — temporary pre-signed access URLs for private objects (S3 & GCS)
  • 🧩 Extensible — extend StorageDriver to build your own backend

Installation

npm install @azversan/storage

Required peer dependencies:

npm install @nestjs/common @nestjs/core reflect-metadata rxjs

Optional peer dependencies — install only the drivers you need:

# AWS S3 / MinIO / Cloudflare R2
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

# Google Cloud Storage
npm install @google-cloud/storage

Quick Start

Local Driver

import { Module } from '@nestjs/common';
import { StorageModule, LocalStorageDriver } from '@azversan/storage';

@Module({
  imports: [
    StorageModule.register({
      name: 'avatars',
      driverClass: LocalStorageDriver,
      options: {
        dest: './uploads/avatars',
        baseUrl: 'http://localhost:3000/uploads/avatars',
      },
    }),
  ],
})
export class AppModule {}

AWS S3 Driver

import { Module } from '@nestjs/common';
import { StorageModule, S3StorageDriver } from '@azversan/storage';

@Module({
  imports: [
    StorageModule.register({
      name: 'documents',
      driverClass: S3StorageDriver,
      options: {
        region: 'us-east-1',
        bucket: 'my-documents-bucket',
        accessKeyId: process.env.AWS_ACCESS_KEY_ID,
        secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
      },
    }),
  ],
})
export class AppModule {}

MinIO / S3-compatible:

StorageModule.register({
  name: 'minio',
  driverClass: S3StorageDriver,
  options: {
    region: 'us-east-1',
    bucket: 'my-bucket',
    endpoint: 'http://localhost:9000',
    forcePathStyle: true,
    accessKeyId: 'minioadmin',
    secretAccessKey: 'minioadmin',
  },
});

Google Cloud Storage Driver

import { Module } from '@nestjs/common';
import { StorageModule, GcsStorageDriver } from '@azversan/storage';

@Module({
  imports: [
    StorageModule.register({
      name: 'media',
      driverClass: GcsStorageDriver,
      options: {
        projectId: 'my-gcp-project',
        bucket: 'my-media-bucket',
        keyFilename: '/path/to/service-account.json',
        publicRead: true,
      },
    }),
  ],
})
export class AppModule {}

Registration Methods

register()

Register a single storage driver synchronously.

StorageModule.register({
  name: 'avatars',
  driverClass: LocalStorageDriver,
  options: { dest: './uploads' },
});

registerMany()

Register multiple drivers in a single call.

StorageModule.registerMany([
  {
    name: 'avatars',
    driverClass: LocalStorageDriver,
    options: { dest: './uploads/avatars' },
  },
  {
    name: 'documents',
    driverClass: S3StorageDriver,
    options: { region: 'us-east-1', bucket: 'docs-bucket' },
  },
]);

registerAsync()

Register a driver asynchronously — useful when options come from ConfigService or another async provider.

useFactory:

StorageModule.registerAsync({
  name: 'avatars',
  driverClass: S3StorageDriver,
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    region: config.get('AWS_REGION'),
    bucket: config.get('S3_BUCKET'),
    accessKeyId: config.get('AWS_ACCESS_KEY_ID'),
    secretAccessKey: config.get('AWS_SECRET_ACCESS_KEY'),
  }),
});

useClass:

@Injectable()
class StorageConfigFactory implements StorageOptionsFactory<S3Options> {
  constructor(private readonly config: ConfigService) {}

  createStorageOptions(): S3Options {
    return {
      region: this.config.get('AWS_REGION'),
      bucket: this.config.get('S3_BUCKET'),
    };
  }
}

StorageModule.registerAsync({
  name: 'avatars',
  driverClass: S3StorageDriver,
  useClass: StorageConfigFactory,
});

useExisting:

StorageModule.registerAsync({
  name: 'avatars',
  driverClass: S3StorageDriver,
  imports: [ConfigFactoryModule],
  useExisting: StorageConfigFactory,
});

registerManyAsync()

Register multiple drivers asynchronously in a single call.

StorageModule.registerManyAsync([
  {
    name: 'avatars',
    driverClass: LocalStorageDriver,
    useFactory: () => ({ dest: './uploads/avatars' }),
  },
  {
    name: 'backups',
    driverClass: GcsStorageDriver,
    imports: [ConfigModule],
    inject: [ConfigService],
    useFactory: (config: ConfigService) => ({
      projectId: config.get('GCS_PROJECT_ID'),
      bucket: config.get('GCS_BUCKET'),
    }),
  },
]);

Injecting a Driver

Using @InjectStorage()

Inject a named driver directly into a constructor using the @InjectStorage() parameter decorator.

import { Injectable } from '@nestjs/common';
import { InjectStorage, LocalStorageDriver } from '@azversan/storage';

@Injectable()
export class AvatarService {
  constructor(@InjectStorage('avatars') private readonly storage: LocalStorageDriver) {}

  async uploadAvatar(userId: string, buffer: Buffer): Promise<string> {
    const result = await this.storage.upload({
      key: `avatars/${userId}.png`,
      buffer,
      mimeType: 'image/png',
    });
    return result.url ?? result.key;
  }
}

Using StorageRegistry

Resolve any registered driver by name at runtime using the StorageRegistry service.

import { Injectable } from '@nestjs/common';
import { StorageRegistry, S3StorageDriver } from '@azversan/storage';

@Injectable()
export class FileService {
  constructor(private readonly storageRegistry: StorageRegistry) {}

  getDriver(name: string) {
    return this.storageRegistry.get<S3StorageDriver>(name);
  }
}

Note: StorageRegistry is globally provided once StorageModule has been imported in your root AppModule. No additional imports are needed in feature modules.

Driver API

All drivers extend StorageDriver and implement the following interface.

upload()

Upload a raw buffer to the storage backend.

const result = await storage.upload({
  key: 'images/photo.jpg',
  buffer: fileBuffer,
  mimeType: 'image/jpeg',
  metadata: { uploadedBy: 'user-42' },
});

// result: { key, driver, size, url? }

delete()

Delete an object by its storage key.

await storage.delete('images/photo.jpg');

exists()

Check whether an object exists at the given key. Returns true or false.

const found = await storage.exists('images/photo.jpg');

getSignedUrl()

Generate a temporary pre-signed URL for private object access. Available on S3StorageDriver and GcsStorageDriver.

// S3: ttl in seconds (default: 900 = 15 min)
const url = await s3Driver.getSignedUrl('private/report.pdf', 300);

// GCS: ttl in milliseconds (default: 900_000 = 15 min)
const url = await gcsDriver.getSignedUrl('private/report.pdf', 300_000);

Driver Reference

LocalOptions

Option Type Required Description
dest string Absolute or relative path where files will be stored on disk.
baseUrl string Base URL prepended to the key to build a public URL on upload.

S3Options

Option Type Required Description
region string AWS region (e.g. us-east-1).
bucket string S3 bucket name.
accessKeyId string AWS access key ID. Falls back to environment / IAM role if omitted.
secretAccessKey string AWS secret access key. Falls back to environment / IAM role if omitted.
endpoint string Custom endpoint for S3-compatible services (e.g. http://localhost:9000 for MinIO).
forcePathStyle boolean Force path-style URLs. Required for MinIO. Defaults to false.
publicRead boolean Tag uploads as public-read and return a permanent URL. Defaults to false.

GcsOptions

Option Type Required Description
projectId string GCP project ID.
bucket string GCS bucket name.
keyFilename string Path to a service-account key file. Use this or credentials, not both.
credentials object Inline service-account credentials. Use this or keyFilename, not both.
publicRead boolean Make uploaded objects world-readable and return a permanent URL. Defaults to false.

Writing a Custom Driver

Extend StorageDriver to implement any backend you need.

import { StorageDriver, UploadOptions, UploadResult } from '@azversan/storage';

export interface MyOptions {
  endpoint: string;
  token: string;
}

export class MyCustomDriver extends StorageDriver<MyOptions> {
  readonly driver = 'my-custom';

  async upload(options: UploadOptions): Promise<UploadResult> {
    // your upload logic here
    return { key: options.key, driver: this.driver, size: options.buffer.length };
  }

  async delete(key: string): Promise<void> {
    // your delete logic here
  }

  async exists(key: string): Promise<boolean> {
    // your exists check here
    return false;
  }
}

Then register it like any other driver:

StorageModule.register({
  name: 'custom',
  driverClass: MyCustomDriver,
  options: { endpoint: 'https://my-storage.io', token: process.env.MY_TOKEN },
});

Scripts

# Build the package
npm run build

# Build in watch mode
npm run build:watch

# Run all tests
npm run test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage report
npm run test:cov

# Run ESLint (with auto-fix)
npm run lint

# Format source files with Prettier
npm run format

Project Structure

src/
└── storage/
    ├── drivers/
    │   ├── __tests__/
    │   │   ├── gcs.driver.spec.ts
    │   │   ├── local.driver.spec.ts
    │   │   └── s3.driver.spec.ts
    │   ├── gcs.driver.ts
    │   ├── local.driver.ts
    │   └── s3.driver.ts
    ├── services/
    │   └── registry/
    │       └── registry.service.ts
    ├── __tests__/
    │   ├── storage.decorator.spec.ts
    │   └── storage.module.spec.ts
    ├── storage.constants.ts
    ├── storage.decorator.ts
    ├── storage.interface.ts
    ├── storage.module.ts
    └── storage.utils.ts

Support

If this project helps you or your team, please consider supporting its continued development. Your support helps maintain the package, improve documentation, and keep the project actively maintained. 🚀

Ways to Support

Thank you for supporting open-source software and helping this project grow ❤️

License

MIT License © Agung Dirgantara

About

A flexible, production-ready NestJS storage module with support for Local, AWS S3/S3-compatible, and Google Cloud Storage drivers.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors