Skip to content

UtilKit/nestjs-minio-backend

Repository files navigation

NestJS MinIO Backend

NPM Version Downloads Stats License TypeScript NestJS MinIO

A powerful and flexible NestJS module for integrating MinIO object storage into your NestJS applications. This package provides a seamless way to interact with MinIO, an open-source object storage service compatible with Amazon S3.

Table of Contents

Features

  • πŸš€ Easy integration with NestJS applications
  • 🎯 Powerful decorators for blazing-fast implementation
    • @FileUpload() - Handles multiple file uploads with built-in validation
    • @FileField() - Swagger-ready DTO field decorator
    • @FileSchemaField() - Mongoose schema integration for file fields
  • πŸ—ƒοΈ @FileColumn() decorator for TypeORM or any class-based model
  • πŸ“ Complete MinIO operations support (upload, download, delete, etc.)
  • πŸ”§ Configurable module options
  • 🎯 TypeScript support
  • πŸ“ Swagger documentation support
  • πŸ”„ RxJS integration
  • 🧩 Optional @nestjs/mongoose integration (only required if you use @FileSchemaField)
  • πŸ€– Automatic presigned URL detection even for raw QueryBuilder results
  • πŸ”’ Explicit minio:// prefix format for safe URL transformation

Migration Guide (v1.x β†’ v2.0.0)

⚠️ Breaking Changes

File Path Format Change: Starting from v2.0.0, all file paths are stored in minio://bucket-name/file-path format instead of bucket-name/file-path.

What Changed?

  • uploadFile() now returns minio://bucket/file format
  • Only strings starting with minio:// are automatically transformed to presigned URLs
  • This prevents accidental transformation of other URLs (like otpauth://, http://, https://, etc.)
  • Removed backward compatibility with bucket/file format

Why This Change?

The previous version had an issue where any string containing a / character could be incorrectly identified as a MinIO path, causing unwanted URL transformations. For example, OTP URLs like otpauth://totp/NodeLink:user?secret=... were being incorrectly transformed.

The new minio:// prefix ensures:

  • βœ… Explicit identification: Only intentionally marked file paths are transformed
  • βœ… Safety: Other URLs remain untouched
  • βœ… Clarity: Developers can clearly see which fields are MinIO file references

Migration Steps

  1. Update your database: Migrate existing file paths from bucket/file to minio://bucket/file

    -- Example SQL migration (adjust for your database)
    -- PostgreSQL
    UPDATE users SET avatar = CONCAT('minio://', avatar) WHERE avatar NOT LIKE 'minio://%' AND avatar LIKE '%/%';
    
    -- MySQL
    UPDATE users SET avatar = CONCAT('minio://', avatar) WHERE avatar NOT LIKE 'minio://%' AND avatar LIKE '%/%';
    
    -- MongoDB
    db.users.updateMany(
      { avatar: { $exists: true, $not: /^minio:\/\// } },
      [{ $set: { avatar: { $concat: ["minio://", "$avatar"] } } }]
    );
  2. Update your code: If you manually construct file paths, use minio:// prefix:

    // Before (v1.x)
    const filePath = `${bucketName}/${fileName}`;
    
    // After (v2.0.0)
    const filePath = `minio://${bucketName}/${fileName}`;
  3. Delete operations: The deleteFile() method now accepts both formats for easier migration:

    // Both work:
    await minioService.deleteFile('minio://bucket/file');
    await minioService.deleteFile('bucket', 'file');
  4. Manual file path construction: If you're building file paths manually:

    // Before (v1.x)
    const storedPath = await minioService.uploadFile(file, 'my-bucket');
    // Returns: "my-bucket/file-name.jpg"
    
    // After (v2.0.0)
    const storedPath = await minioService.uploadFile(file, 'my-bucket');
    // Returns: "minio://my-bucket/file-name.jpg"

Benefits

  • βœ… No more accidental URL transformations: Other URLs (OTP, HTTP, etc.) are never touched
  • βœ… Explicit file path identification: Clear distinction between MinIO paths and other URLs
  • βœ… Works seamlessly with query builders: Automatic transformation still works for nested objects
  • βœ… Better type safety: Clearer intent in your codebase

Need Help?

If you encounter any issues during migration, please:

Installation

npm install nestjs-minio-backend

Requirements

  • Node.js >= 20.15.0
  • NestJS >= 11.0.0
  • MinIO Server (running instance)

Peer Dependencies

This module requires the following peer dependencies:

{
  "@nestjs/common": "^11.0.12",
  "@nestjs/core": "^11.0.12",
  "minio": "^8.0.5",
  "rxjs": "^7.8.2",
  "@nestjs/mongoose": "^11.0.2",
  "@nestjs/platform-express": "^11.0.12",
  "@nestjs/swagger": "^11.0.7",
  "class-validator": "^0.14.1"
}

Quick Start (Using Decorators)

  1. First, set up your MinIO configuration:

Create a config/minio.config.ts file:

import { registerAs } from '@nestjs/config';

export default registerAs('minio', () => ({
  endPoint: process.env.MINIO_ENDPOINT || 'localhost:9000', // Format: "host" or "host:port"
  externalEndPoint:
    process.env.MINIO_EXTERNAL_ENDPOINT ||
    process.env.MINIO_ENDPOINT ||
    'localhost:9000', // Format: "host" or "host:port"
  useSSL: process.env.MINIO_USE_HTTPS === 'true',
  externalUseSSL: process.env.MINIO_EXTERNAL_ENDPOINT_USE_HTTPS === 'true',
  accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
  secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin',
  urlExpiryHours: parseInt(process.env.MINIO_URL_EXPIRY_HOURS || '2', 10),
  buckets: {
    private: [
      process.env.MINIO_MEDIA_FILES_BUCKET || 'media-files-bucket',
      process.env.MINIO_CHAIN_ICONS_BUCKET || 'chain-icons',
    ],
    public: [process.env.MINIO_STATIC_FILES_BUCKET || 'static-files-bucket'],
  },
  region: process.env.MINIO_REGION,
}));

Create a .env file in your project root:

MINIO_ENDPOINT=minio:9000
MINIO_EXTERNAL_ENDPOINT=minio.example.com:9000
MINIO_USE_HTTPS=false
MINIO_ACCESS_KEY=your-access-key
MINIO_SECRET_KEY=your-secret-key
MINIO_MEDIA_FILES_BUCKET=media-files-bucket
MINIO_STATIC_FILES_BUCKET=static-files-bucket
MINIO_URL_EXPIRY_HOURS=2

Then, in your app.module.ts:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MinioModule } from 'nestjs-minio-backend';
import minioConfig from './config/minio.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      load: [minioConfig],
    }),
    MinioModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => configService.get('minio'),
    }),
  ],
})
export class AppModule {}

This configuration approach provides:

  • πŸ” Environment-based configuration
  • 🎯 Type-safe configuration using TypeScript
  • πŸ”„ Default values for local development
  • πŸ“ Organized bucket management
  • 🌐 Support for different endpoints (internal/external)
  1. Create your DTO with file field:
import { FileField } from 'nestjs-minio-backend';

export class CreateUserDto {
  @FileField({
    bucketName: 'profiles',
    required: true,
    description: 'User profile picture'
  })
  profilePicture: Express.Multer.File;
}
  1. Use the FileUpload decorator in your controller:
import { FileUpload } from 'nestjs-minio-backend';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UserController {
  @Post()
  @FileUpload([
    { name: 'profilePicture', bucketName: 'profiles', required: true }
  ])
  async createUser(@Body() createUserDto: CreateUserDto) {
    // The file is automatically uploaded to MinIO
    // You can access the file URL from the DTO
    return createUserDto;
  }

  @Post('multiple')
  @FileUpload([
    { name: 'documents', bucketName: 'docs', maxCount: 3 }
  ])
  async uploadMultiple(@UploadedFiles() files: Array<Express.Multer.File>) {
    return files; // Files are automatically uploaded to MinIO
  }
}
  1. (Optional) Add file fields to your persistence models:

TypeORM example

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { FileColumn } from 'nestjs-minio-backend';

@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @FileColumn({ bucketName: 'profiles' })
  @Column({ nullable: true })
  avatar?: string; // Stores the MinIO object path (minio://bucket/objectName)
}

Mongoose example

import { FileSchemaField } from 'nestjs-minio-backend';

@Schema()
export class User {
  @FileSchemaField({
    bucketName: 'profiles',
    required: true
  })
  avatar: string; // Automatically stores the MinIO object path (minio://bucket/objectName)
}

These decorators provide:

  • πŸš€ Zero-configuration file uploads
  • πŸ“ Automatic Swagger documentation
  • βœ… Built-in validation
  • πŸ”„ Seamless MongoDB integration
  • πŸ€– Automatic presigned URL generation even for raw QueryBuilder objects (only for strings starting with minio://)
  • 🎯 Type safety with TypeScript
  • 🧩 Optional Mongoose dependency (install @nestjs/mongoose only if you plan to use @FileSchemaField)

Configuration

The module accepts the following configuration options:

interface IMinioModuleOptions {
  // Required options
  endPoint: string;          // MinIO server endpoint (format: "host" or "host:port", e.g., "minio:9000")
  useSSL: boolean;          // Whether to use SSL for connection
  accessKey: string;        // MinIO access key
  secretKey: string;        // MinIO secret key
  urlExpiryHours: number;   // Expiry time for signed URLs in hours

  // Optional options
  region?: string;          // MinIO region
  externalEndPoint?: string; // External endpoint for public access (format: "host" or "host:port")
  externalUseSSL?: boolean; // Whether to use SSL for external endpoint

  // Bucket configuration
  buckets: {
    private: string[];      // Array of private bucket names
    public: string[];       // Array of public bucket names
  };
}

Configuration Example

const minioConfig: IMinioModuleOptions = {
  endPoint: 'minio.example.com:9000', // Port included in endpoint
  useSSL: false,
  accessKey: 'your-access-key',
  secretKey: 'your-secret-key',
  urlExpiryHours: 2,
  
  // Optional settings
  region: 'us-east-1',
  externalEndPoint: 'public.minio.example.com:9000', // Port included in endpoint
  externalUseSSL: true,
  
  // Bucket configuration
  buckets: {
    private: [
      'media-files-bucket',
      'user-uploads'
    ],
    public: [
      'static-files-bucket',
      'public-assets'
    ]
  }
};

Environment Variables Example

# Required settings
MINIO_ENDPOINT=minio:9000
MINIO_USE_HTTPS=false
MINIO_ACCESS_KEY=your-access-key
MINIO_SECRET_KEY=your-secret-key
MINIO_URL_EXPIRY_HOURS=2

# Optional settings
MINIO_REGION=us-east-1
MINIO_EXTERNAL_ENDPOINT=minio.example.com:9000
MINIO_EXTERNAL_ENDPOINT_USE_HTTPS=true

# Bucket configuration
MINIO_MEDIA_FILES_BUCKET=media-files-bucket
MINIO_STATIC_FILES_BUCKET=static-files-bucket

Key Features of the Configuration

  • πŸ” Dual Endpoint Support: Configure both internal and external endpoints for flexible access
  • 🌐 SSL Configuration: Separate SSL settings for internal and external endpoints
  • ⏱️ URL Expiry: Configure signed URL expiration time
  • πŸ“ Bucket Organization: Separate configuration for private and public buckets
  • πŸ”„ Default Values: Sensible defaults for local development
  • πŸ›‘οΈ Type Safety: Full TypeScript support with interface definitions

Manual Implementation (Without Decorators)

If you prefer more control over the file handling process, you can use the MinioService directly:

import { MinioService } from 'nestjs-minio-backend';

@Injectable()
export class YourService {
  constructor(private readonly minioService: MinioService) {}

  async uploadFile(file: Express.Multer.File) {
    const bucketName = 'your-bucket';
    const objectName = `${Date.now()}-${file.originalname}`;
    
    await this.minioService.upload(bucketName, objectName, file.buffer);
    return { objectName };
  }
}

Manual File Upload Example

@Post('upload')
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
  const bucketName = 'my-bucket';
  const objectName = `${Date.now()}-${file.originalname}`;
  
  await this.minioService.upload(bucketName, objectName, file.buffer);
  
  return {
    message: 'File uploaded successfully',
    objectName,
  };
}

Manual File Download Example

@Get('download/:objectName')
async downloadFile(@Param('objectName') objectName: string, @Res() res: Response) {
  const bucketName = 'my-bucket';
  const fileBuffer = await this.minioService.download(bucketName, objectName);
  
  res.send(fileBuffer);
}

API Reference

Decorators

@FileUpload()

Handles file uploads with automatic MinIO integration.

@FileUpload([
  { 
    name: string,           // Field name in the request
    bucketName: string,     // MinIO bucket name
    required?: boolean,     // Whether the file is required
    maxCount?: number,      // Maximum number of files
    maxSize?: number,       // Maximum file size in bytes
    mimeTypes?: string[]    // Allowed MIME types
  }
])

@FileField()

Swagger-ready DTO field decorator for file uploads.

@FileField({
  bucketName: string,       // MinIO bucket name
  required?: boolean,       // Whether the field is required
  description?: string      // Swagger description
})

@FileSchemaField()

Mongoose schema integration for file fields (wraps @Prop plus @FileColumn metadata).

@FileSchemaField({
  bucketName: string,       // MinIO bucket name
  required?: boolean        // Whether the field is required
})

@FileColumn()

Database-agnostic decorator for marking entity properties that store MinIO object references. Works with TypeORM, Mongoose, or any class-based model.

@FileColumn({
  bucketName?: string        // Optionally enforce a specific bucket
})

Contributing

  1. Fork it (https://github.com/UtilKit/nestjs-minio-backend/fork)
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -am 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Create a new Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Mishhub

Support

About

A powerful and flexible NestJS module for integrating MinIO object storage into your NestJS applications.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published