npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ciphercross/nestjs-upload

v1.0.2

Published

NestJS library for uploading images to AWS S3 and Google Cloud Storage with advanced validation.

Readme

NestJS Upload Library

A comprehensive library for uploading images and files to AWS S3 or Google Cloud Storage with advanced validation, image optimization, rate limiting, and caching for NestJS applications.

📋 Table of Contents

✨ Features

  • AWS S3 and Google Cloud Storage Support - Flexible provider choice
  • Advanced File Validation - Size, MIME type, format validation
  • Image Validation - Dimensions, aspect ratio, format validation
  • Automatic Image Optimization - Size reduction, WebP conversion
  • Rate Limiting - Protection against abuse (in-memory or Redis)
  • Presigned URL Caching - Performance improvement
  • Private File Support - Presigned URLs for secure access
  • Batch Operations - Upload/delete multiple files simultaneously
  • Stream Upload - Support for large files
  • File Metadata - Custom headers and metadata
  • Full TypeScript Support - Typed methods and interfaces
  • Swagger/OpenAPI Documentation - Automatic documentation generation
  • REST API Endpoints - Ready-to-use endpoints for upload/delete
  • Operation Logging - Detailed logging of all operations

📦 Installation

npm install nestjs-upload

Dependencies

The library requires the following peer dependencies:

npm install @nestjs/common @nestjs/config @nestjs/platform-express

For Swagger documentation (optional):

npm install @nestjs/swagger

For image optimization (optional, but recommended):

npm install sharp

For Redis rate limiting (optional):

npm install ioredis

🚀 Quick Start

1. AWS S3 Configuration

Using Default Credentials (Recommended):

AWS SDK automatically uses credentials from:

  • Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  • ~/.aws/credentials file
  • IAM roles (on EC2 instances)
import { Module } from '@nestjs/common';
import { UploadModule, StorageProvider } from 'nestjs-upload';

@Module({
  imports: [
    UploadModule.forRoot({
      provider: StorageProvider.AWS,
      aws: {
        bucket: 'my-bucket-name',
        region: 'us-east-1',
        // Credentials will be automatically loaded from ~/.aws/credentials
        // or environment variables
      },
      validation: {
        maxFileSize: 5 * 1024 * 1024, // 5MB
        allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
        imageValidation: {
          minWidth: 100,
          maxWidth: 2000,
          minHeight: 100,
          maxHeight: 2000,
          aspectRatio: {
            width: 16,
            height: 9,
            tolerance: 0.1,
          },
          allowedFormats: ['jpeg', 'png', 'webp'],
        },
      },
      imageOptimization: {
        enabled: true,
        maxWidth: 1920,
        maxHeight: 1080,
        quality: 85,
        format: 'auto', // or ImageFormat.WEBP, ImageFormat.JPEG, ImageFormat.PNG
        convertToWebP: false,
      },
      defaultPublic: true,
      rateLimit: {
        enabled: true,
        type: 'memory', // or 'redis'
        windowMs: 60000, // 1 minute
        maxRequests: 10,
      },
      cache: {
        enabled: true,
        ttl: 3600, // 1 hour
        maxSize: 1000,
      },
    }),
  ],
})
export class AppModule {}

2. Google Cloud Storage Configuration

Using Application Default Credentials (Recommended):

Google Cloud SDK automatically uses credentials from:

  • GOOGLE_APPLICATION_CREDENTIALS environment variable
  • ~/.config/gcloud/application_default_credentials.json (after gcloud auth application-default login)
  • GCE/Cloud Run metadata service
import { Module } from '@nestjs/common';
import { UploadModule, StorageProvider } from 'nestjs-upload';

@Module({
  imports: [
    UploadModule.forRoot({
      provider: StorageProvider.GCS,
      gcs: {
        bucket: 'my-bucket-name',
        projectId: 'my-project-id',
        // Credentials will be automatically loaded from Application Default Credentials
      },
      validation: {
        maxFileSize: 10 * 1024 * 1024, // 10MB
        allowedMimeTypes: ['image/jpeg', 'image/png'],
        imageValidation: {
          minWidth: 200,
          maxWidth: 4000,
          minHeight: 200,
          maxHeight: 4000,
        },
      },
    }),
  ],
})
export class AppModule {}

Setting Up Credentials

AWS Credentials

Option 1: Using ~/.aws/credentials file (Recommended for local development)

Create ~/.aws/credentials file:

[default]
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY

Option 2: Using environment variables

export AWS_ACCESS_KEY_ID=your_access_key_id
export AWS_SECRET_ACCESS_KEY=your_secret_access_key
export AWS_REGION=us-east-1

Option 3: Using explicit credentials in config (not recommended)

aws: {
  bucket: 'my-bucket',
  region: 'us-east-1',
  accessKey: 'your-access-key',
  secretKey: 'your-secret-key',
}

Google Cloud Credentials

Option 1: Using Application Default Credentials (Recommended)

gcloud auth application-default login

This creates credentials at ~/.config/gcloud/application_default_credentials.json

Option 2: Using GOOGLE_APPLICATION_CREDENTIALS environment variable

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json

Option 3: Using explicit key file in config

gcs: {
  bucket: 'my-bucket',
  projectId: 'my-project-id',
  keyFilename: './path/to/service-account-key.json',
}

⚙️ Configuration

Full Configuration with All Options

import { UploadModule, StorageProvider, ImageFormat } from 'nestjs-upload';

UploadModule.forRoot({
  // Required parameters
  provider: StorageProvider.AWS, // or StorageProvider.GCS
  
  // AWS configuration (if provider === StorageProvider.AWS)
  aws: {
    bucket: 'my-bucket',
    region: 'us-east-1',
    // Optional: Explicit credentials (if not provided, uses default credential chain)
    // accessKey: process.env.AWS_ACCESS_KEY_ID,
    // secretKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
  
  // GCS configuration (if provider === StorageProvider.GCS)
  gcs: {
    bucket: 'my-bucket',
    projectId: 'my-project-id',
    // Optional: Explicit credentials (if not provided, uses Application Default Credentials)
    // keyFilename: './path/to/service-account-key.json',
    // or
    // credentials: { client_email: '...', private_key: '...', project_id: '...' }
  },
  
  // File validation
  validation: {
    maxFileSize: 5 * 1024 * 1024, // Maximum size in bytes
    allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
    imageValidation: {
      minWidth: 100,
      maxWidth: 2000,
      minHeight: 100,
      maxHeight: 2000,
      aspectRatio: {
        width: 16,
        height: 9,
        tolerance: 0.1, // 10% deviation
      },
      allowedFormats: ['jpeg', 'png', 'webp'],
    },
  },
  
  // Image optimization
  imageOptimization: {
    enabled: true,
    maxWidth: 1920,
    maxHeight: 1080,
    quality: 85, // 1-100
    format: ImageFormat.AUTO, // 'auto' | ImageFormat.JPEG | ImageFormat.PNG | ImageFormat.WEBP
    convertToWebP: false, // If true, all images are converted to WebP
  },
  
  // Files are public by default
  defaultPublic: true,
  
  // Rate Limiting
  rateLimit: {
    enabled: true,
    type: 'memory', // 'memory' | 'redis'
    windowMs: 60000, // Time window in milliseconds
    maxRequests: 10, // Maximum number of requests in the window
    // Redis configuration (if type === 'redis')
    redis: {
      host: 'localhost',
      port: 6379,
      // or
      // client: redisClient, // Existing Redis client
    },
  },
  
  // Presigned URL caching
  cache: {
    enabled: true,
    ttl: 3600, // Time to live in seconds
    maxSize: 1000, // Maximum number of entries in cache
  },
})

🌐 API Endpoints

POST /upload

Uploads a file to the selected storage with validation and optional optimization.

Request:

  • Content-Type: multipart/form-data
  • file (required): File to upload
  • folder (optional): Folder path in bucket (e.g., images/products)
  • isPublic (optional): Whether the file is public (default: true)
  • metadata (optional): Object with metadata:
    • contentType: File MIME type
    • cacheControl: Cache-Control header value
    • contentDisposition: Content-Disposition header value
    • contentEncoding: Content-Encoding header value
    • metadata: Custom metadata (key-value pairs)

Response:

{
  "imgUrl": "https://bucket.s3.region.amazonaws.com/uuid.jpg",
  "key": "images/products/uuid.jpg",
  "bucket": "my-bucket-name",
  "isPublic": true
}

Example with curl:

curl -X POST http://localhost:3000/upload \
  -F "[email protected]" \
  -F "folder=images/products" \
  -F "isPublic=true" \
  -F "metadata[contentType]=image/jpeg" \
  -F "metadata[cacheControl]=public, max-age=3600"

Example with FormData (JavaScript):

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('folder', 'images/products');
formData.append('isPublic', 'true');
formData.append('metadata[contentType]', 'image/jpeg');
formData.append('metadata[cacheControl]', 'public, max-age=3600');

fetch('http://localhost:3000/upload', {
  method: 'POST',
  body: formData,
});

DELETE /upload/:key

Deletes a file from storage by key.

Parameters:

  • key (path parameter): File key/path in storage

Response:

{
  "success": true,
  "message": "File deleted successfully"
}

Example:

curl -X DELETE http://localhost:3000/upload/images/products/uuid.jpg

GET /upload/presigned-url/:key

Generates a presigned URL for accessing a private file.

Parameters:

  • key (path parameter): File key/path in storage

Query Parameters:

  • expiresIn (optional): URL expiration time in seconds (default: 3600, max: 604800)
  • contentType (optional): Content-Type for the presigned URL

Response:

{
  "url": "https://bucket.s3.region.amazonaws.com/file.jpg?X-Amz-Algorithm=...",
  "key": "images/products/uuid.jpg",
  "expiresIn": 3600
}

Example:

curl "http://localhost:3000/upload/presigned-url/images/products/uuid.jpg?expiresIn=7200&contentType=image/jpeg"

💻 Service Usage

Injecting the Service

import { Injectable } from '@nestjs/common';
import { UploadService } from 'nestjs-upload';

@Injectable()
export class MyService {
  constructor(private readonly uploadService: UploadService) {}
}

UploadService Methods

uploadFile()

Uploads a file from buffer with validation and optional optimization.

async uploadFile(
  dataBuffer: Buffer,
  filename: string,
  folder?: string,
  options?: UploadOptions,
): Promise<{ imgUrl: string; key: string; bucket: string; isPublic?: boolean }>

Parameters:

  • dataBuffer: Buffer with file data
  • filename: Original filename
  • folder: Optional folder in bucket
  • options: Upload options:
    • isPublic: Whether the file is public (default: true)
    • metadata: File metadata:
      • contentType: MIME type
      • cacheControl: Cache-Control header
      • contentDisposition: Content-Disposition header
      • contentEncoding: Content-Encoding header
      • metadata: Custom metadata (Record<string, string>)

Example:

const result = await this.uploadService.uploadFile(
  buffer,
  'image.jpg',
  'images/products',
  {
    isPublic: true,
    metadata: {
      contentType: 'image/jpeg',
      cacheControl: 'public, max-age=3600',
      metadata: {
        author: 'John Doe',
        category: 'product',
      },
    },
  },
);

console.log(result.imgUrl); // File URL
console.log(result.key); // File key
console.log(result.bucket); // Bucket name

uploadPublicFile()

Uploads a public file (backward compatibility method).

async uploadPublicFile(
  dataBuffer: Buffer,
  filename: string,
  folder?: string,
): Promise<{ imgUrl: string; key: string; bucket: string }>

Example:

const result = await this.uploadService.uploadPublicFile(
  buffer,
  'image.jpg',
  'images/products',
);

uploadFileStream()

Uploads a file from stream (useful for large files).

async uploadFileStream(
  stream: Readable,
  filename: string,
  folder?: string,
  options?: UploadOptions,
): Promise<{ imgUrl: string; key: string; bucket: string; isPublic?: boolean }>

Example:

import { createReadStream } from 'fs';

const stream = createReadStream('./large-file.zip');
const result = await this.uploadService.uploadFileStream(
  stream,
  'large-file.zip',
  'downloads',
  { isPublic: false },
);

uploadFiles()

Uploads multiple files simultaneously (batch upload).

async uploadFiles(
  files: Array<{ buffer: Buffer; filename: string; folder?: string }>,
  options?: UploadOptions,
): Promise<Array<{ imgUrl: string; key: string; bucket: string; isPublic?: boolean }>>

Example:

const files = [
  { buffer: buffer1, filename: 'image1.jpg', folder: 'images' },
  { buffer: buffer2, filename: 'image2.png', folder: 'images' },
  { buffer: buffer3, filename: 'document.pdf', folder: 'documents' },
];

const results = await this.uploadService.uploadFiles(files, {
  isPublic: true,
});

results.forEach((result, index) => {
  console.log(`File ${index + 1}: ${result.imgUrl}`);
});

deleteFile()

Deletes a file from storage.

async deleteFile(
  key: string,
  ignoreNotFound?: boolean,
): Promise<boolean>

Parameters:

  • key: File key/path in storage
  • ignoreNotFound: If true, doesn't throw error if file not found (default: false)

Example:

// With error thrown if file not found
await this.uploadService.deleteFile('images/products/uuid.jpg');

// Without error thrown
const deleted = await this.uploadService.deleteFile(
  'images/products/uuid.jpg',
  true,
);
if (deleted) {
  console.log('File deleted');
} else {
  console.log('File not found');
}

deleteFiles()

Deletes multiple files simultaneously (batch delete).

async deleteFiles(
  keys: string[],
  ignoreNotFound?: boolean,
): Promise<boolean[]>

Example:

const keys = [
  'images/products/uuid1.jpg',
  'images/products/uuid2.png',
  'images/products/uuid3.webp',
];

const results = await this.uploadService.deleteFiles(keys, true);
// results = [true, true, false] - last file not found

getPresignedUrl()

Generates a presigned URL for accessing a private file.

async getPresignedUrl(
  key: string,
  options?: PresignedUrlOptions,
): Promise<string>

Parameters:

  • key: File key/path in storage
  • options: Presigned URL options:
    • expiresIn: URL expiration time in seconds (default: 3600)
    • contentType: Content-Type for URL

Example:

// Basic presigned URL (1 hour)
const url = await this.uploadService.getPresignedUrl(
  'private/documents/file.pdf',
);

// With custom expiration and Content-Type
const url = await this.uploadService.getPresignedUrl(
  'private/images/photo.jpg',
  {
    expiresIn: 7200, // 2 hours
    contentType: 'image/jpeg',
  },
);

fileExists()

Checks if a file exists in storage.

async fileExists(key: string): Promise<boolean>

Example:

const exists = await this.uploadService.fileExists('images/products/uuid.jpg');
if (exists) {
  console.log('File exists');
}

getFileMetadata()

Gets file metadata.

async getFileMetadata(key: string): Promise<FileMetadata | null>

Example:

const metadata = await this.uploadService.getFileMetadata(
  'images/products/uuid.jpg',
);

if (metadata) {
  console.log('Size:', metadata.size);
  console.log('Content-Type:', metadata.contentType);
  console.log('Last Modified:', metadata.lastModified);
  console.log('ETag:', metadata.etag);
  console.log('Custom metadata:', metadata.metadata);
}

✅ Validation

File Validation

validation: {
  maxFileSize: 5 * 1024 * 1024, // Maximum size in bytes
  allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp'], // Allowed MIME types
}

Image Validation

imageValidation: {
  minWidth: 100,        // Minimum width in pixels
  maxWidth: 2000,       // Maximum width in pixels
  minHeight: 100,       // Minimum height in pixels
  maxHeight: 2000,      // Maximum height in pixels
  aspectRatio: {        // Aspect ratio
    width: 16,
    height: 9,
    tolerance: 0.1,     // Allowed deviation (10%)
  },
  allowedFormats: ['jpeg', 'png', 'webp'], // Allowed formats
}

Example validation error:

{
  "statusCode": 400,
  "message": "Image validation failed: Image width must be at least 100px, Image aspect ratio must be 16:9"
}

🖼️ Image Optimization

The library supports automatic image optimization using Sharp.

Configuration

imageOptimization: {
  enabled: true,
  maxWidth: 1920,        // Maximum width after optimization
  maxHeight: 1080,       // Maximum height after optimization
  quality: 85,           // Image quality (1-100)
  format: 'auto',         // Output format: 'auto' | ImageFormat.JPEG | ImageFormat.PNG | ImageFormat.WEBP
  convertToWebP: false,   // If true, all images are converted to WebP
}

Image Formats

import { ImageFormat } from 'nestjs-upload';

// Available formats
ImageFormat.JPEG // 'jpeg'
ImageFormat.JPG  // 'jpg' (maps to JPEG)
ImageFormat.PNG  // 'png'
ImageFormat.WEBP // 'webp'

Optimization Examples

Automatic format (preserves original format):

imageOptimization: {
  enabled: true,
  format: 'auto',
  maxWidth: 1920,
  quality: 85,
}

Force WebP conversion:

imageOptimization: {
  enabled: true,
  convertToWebP: true,
  quality: 85,
}

Convert to JPEG:

imageOptimization: {
  enabled: true,
  format: ImageFormat.JPEG,
  quality: 90,
}

Without resizing (compression only):

imageOptimization: {
  enabled: true,
  quality: 85,
  // maxWidth and maxHeight not specified
}

Using ImageOptimizer Directly

import { ImageOptimizer } from 'nestjs-upload';

const optimizer = new ImageOptimizer({
  enabled: true,
  maxWidth: 1920,
  quality: 85,
});

const result = await optimizer.optimize(buffer, 'image/jpeg');
console.log(`Original: ${buffer.length} bytes`);
console.log(`Optimized: ${result.size} bytes`);
console.log(`Reduction: ${((1 - result.size / buffer.length) * 100).toFixed(1)}%`);

🚦 Rate Limiting

The library supports rate limiting to protect against abuse.

In-Memory Rate Limiter

rateLimit: {
  enabled: true,
  type: 'memory',
  windowMs: 60000,    // 1 minute
  maxRequests: 10,    // Maximum 10 requests per minute
}

Redis Rate Limiter

rateLimit: {
  enabled: true,
  type: 'redis',
  windowMs: 60000,
  maxRequests: 10,
  redis: {
    host: 'localhost',
    port: 6379,
    // or use existing client
    // client: redisClient,
  },
}

Response When Limit Exceeded

{
  "statusCode": 429,
  "message": "Too Many Requests",
  "retryAfter": 30
}

💾 Caching

The library supports caching of presigned URLs to improve performance.

Configuration

cache: {
  enabled: true,
  ttl: 3600,      // Time to live in seconds (1 hour)
  maxSize: 1000,  // Maximum number of entries in cache
}

Using PresignedUrlCacheService Directly

import { PresignedUrlCacheService } from 'nestjs-upload';

const cache = new PresignedUrlCacheService(3600, 1000);

// Store URL
cache.set('key', 'https://...', 3600);

// Get URL
const url = cache.get('key');

// Delete from cache
cache.delete('key');

// Clear entire cache
cache.clear();

📚 Types and Interfaces

Exported Types

import {
  // Enums
  StorageProvider,
  ImageFormat,
  ImageMimeType,
  
  // Interfaces
  UploadConfig,
  AwsConfig,
  GcsConfig,
  ValidationConfig,
  ImageValidationConfig,
  ImageOptimizationConfig,
  CacheConfig,
  RateLimitConfig,
  
  // Services
  UploadService,
  ImageOptimizer,
  PresignedUrlCacheService,
  
  // Validators
  FileValidator,
  ImageValidator,
  
  // Providers
  AwsS3Provider,
  GcsProvider,
  IStorageProvider,
  
  // Guards
  RateLimitGuard,
  
  // Utils
  getMimeType,
  isImageMimeType,
  validateFileKey,
  buildSafeKey,
} from 'nestjs-upload';

Main Interfaces

UploadOptions

interface UploadOptions {
  isPublic?: boolean;
  metadata?: FileMetadataOptions;
}

interface FileMetadataOptions {
  contentType?: string;
  cacheControl?: string;
  contentDisposition?: string;
  contentEncoding?: string;
  metadata?: Record<string, string>;
}

PresignedUrlOptions

interface PresignedUrlOptions {
  expiresIn?: number;    // Expiration time in seconds
  contentType?: string; // Content-Type
}

FileMetadata

interface FileMetadata {
  key: string;
  size?: number;
  contentType?: string;
  lastModified?: Date;
  etag?: string;
  metadata?: Record<string, string>;
}

⚠️ Error Handling

The library throws the following error types:

BadRequestException

Thrown when:

  • Invalid files (size, MIME type)
  • Invalid images (dimensions, aspect ratio)
  • Invalid parameters (file key, folder)

Example:

try {
  await uploadService.uploadFile(buffer, 'file.jpg');
} catch (error) {
  if (error instanceof BadRequestException) {
    console.error('Validation error:', error.message);
  }
}

InternalServerErrorException

Thrown when:

  • Upload/delete errors
  • Storage connection errors
  • Other internal errors

Example:

try {
  await uploadService.uploadFile(buffer, 'file.jpg');
} catch (error) {
  if (error instanceof InternalServerErrorException) {
    console.error('Server error:', error.message);
  }
}

Custom Error Handling

import { Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { BadRequestException, InternalServerErrorException } from '@nestjs/common';

@Catch(BadRequestException, InternalServerErrorException)
export class UploadExceptionFilter implements ExceptionFilter {
  catch(exception: BadRequestException | InternalServerErrorException, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse();
    const status = exception.getStatus();
    
    response.status(status).json({
      statusCode: status,
      message: exception.message,
      timestamp: new Date().toISOString(),
    });
  }
}

📖 Usage Examples

Uploading Image with Optimization

import { Injectable } from '@nestjs/common';
import { UploadService } from 'nestjs-upload';

@Injectable()
export class ImageService {
  constructor(private readonly uploadService: UploadService) {}

  async uploadProductImage(buffer: Buffer, filename: string) {
    const result = await this.uploadService.uploadFile(
      buffer,
      filename,
      'images/products',
      {
        isPublic: true,
        metadata: {
          contentType: 'image/jpeg',
          cacheControl: 'public, max-age=31536000',
          metadata: {
            category: 'product',
          },
        },
      },
    );
    
    return result.imgUrl;
  }
}

Uploading Private Document

async uploadPrivateDocument(buffer: Buffer, filename: string, userId: string) {
  const result = await this.uploadService.uploadFile(
    buffer,
    filename,
    `documents/${userId}`,
    {
      isPublic: false,
      metadata: {
        contentType: 'application/pdf',
        metadata: {
          userId,
          type: 'document',
        },
      },
    },
  );
  
  // Generate presigned URL for access
  const presignedUrl = await this.uploadService.getPresignedUrl(
    result.key,
    { expiresIn: 3600 },
  );
  
  return {
    key: result.key,
    url: presignedUrl,
  };
}

Batch Upload with Error Handling

async uploadMultipleImages(files: Array<{ buffer: Buffer; filename: string }>) {
  const uploadPromises = files.map(file =>
    this.uploadService.uploadFile(
      file.buffer,
      file.filename,
      'images/gallery',
      { isPublic: true },
    ).catch(error => {
      console.error(`Failed to upload ${file.filename}:`, error.message);
      return null;
    }),
  );
  
  const results = await Promise.all(uploadPromises);
  const successful = results.filter(r => r !== null);
  
  return {
    total: files.length,
    successful: successful.length,
    failed: files.length - successful.length,
    urls: successful.map(r => r!.imgUrl),
  };
}

Deleting Files by Pattern

async deleteUserFiles(userId: string) {
  // Get list of user files (requires implementation)
  const userFiles = await this.getUserFiles(userId);
  
  // Delete all files
  const results = await this.uploadService.deleteFiles(
    userFiles.map(f => f.key),
    true, // ignoreNotFound
  );
  
  const deletedCount = results.filter(r => r === true).length;
  
  return {
    total: userFiles.length,
    deleted: deletedCount,
  };
}

Check and Update File

async updateFileIfExists(key: string, newBuffer: Buffer) {
  const exists = await this.uploadService.fileExists(key);
  
  if (!exists) {
    throw new NotFoundException('File not found');
  }
  
  // Delete old file
  await this.uploadService.deleteFile(key);
  
  // Upload new file
  const result = await this.uploadService.uploadFile(
    newBuffer,
    key.split('/').pop()!,
    key.split('/').slice(0, -1).join('/'),
  );
  
  return result;
}

Usage with Express Multer

import { Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { UploadService } from 'nestjs-upload';

@Controller('api/files')
export class FilesController {
  constructor(private readonly uploadService: UploadService) {}

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  async upload(@UploadedFile() file: Express.Multer.File) {
    if (!file) {
      throw new BadRequestException('File is required');
    }

    const result = await this.uploadService.uploadFile(
      file.buffer,
      file.originalname,
      'uploads',
    );

    return {
      success: true,
      url: result.imgUrl,
      key: result.key,
    };
  }
}

🔍 Logging

The library uses NestJS built-in logger for logging:

  • ✅ Successful upload/delete operations
  • ✅ Validation errors
  • ✅ Storage operation errors
  • ✅ Image optimization (before/after size)
  • ✅ Presigned URL caching

Example logs:

[UploadService] File uploaded successfully: images/products/uuid.jpg (public)
[ImageOptimizer] Image optimized: 2048576 -> 512384 bytes (75.0% reduction)
[UploadService] Presigned URL generated for: private/documents/file.pdf

📝 Swagger Documentation

If you use @nestjs/swagger, all endpoints will be automatically documented in Swagger UI.

Example configuration:

import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

const config = new DocumentBuilder()
  .setTitle('My API')
  .setDescription('API description')
  .setVersion('1.0')
  .addTag('upload')
  .build();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

🛠️ Extending Functionality

Creating Custom Storage Provider

import { IStorageProvider, UploadResult, UploadOptions } from 'nestjs-upload';
import { Readable } from 'stream';

export class CustomStorageProvider implements IStorageProvider {
  async upload(
    dataBuffer: Buffer,
    filename: string,
    folder?: string,
    options?: UploadOptions,
  ): Promise<UploadResult> {
    // Your implementation
  }

  // Implement other methods...
}

Using Custom Provider

// In your module
const customProvider = new CustomStorageProvider();

const uploadService = new UploadService(
  customProvider,
  fileValidator,
  imageValidator,
  imageOptimizer,
  urlCache,
);

📄 License

MIT

🤝 Support

If you have questions or issues, please create an issue in the project repository.