@vup/nest-upload Upload Module
A reusable NestJS upload module for monorepo, defaulting to local storage, providing minimal viable encapsulation of Service + Controller. Supports scene-based configuration, file type restrictions, size limits, and more.
Technical Stack
- NestJS - Node.js enterprise framework
- TypeORM - TypeScript ORM
- Multer - File upload middleware
Quick Start
Installation
In NestJS projects, this package is usually available through monorepo workspace and doesn't need separate installation.
Basic Usage
Import and configure in business module:
import { Module } from '@nestjs/common';
import { UploadModule } from '@vup/nest-upload';
@Module({
imports: [
UploadModule.forRoot({
uploadDir: 'uploads',
baseUrl: 'http://localhost:3000/uploads',
}),
],
})
export class AppModule {}Upload File
After configuration, you can use the upload interface:
# Upload file
POST /upload
Content-Type: multipart/form-data
file: [file]Configuration Options
UploadModuleOptions
| Parameter | Description | Type | Default |
|---|---|---|---|
| uploadDir | Upload directory (relative to project root) | string | 'uploads' |
| baseUrl | Access prefix (for concatenating file URL) | string | - |
| maxSize | Maximum file size (bytes) | number | 10 * 1024 * 1024 (10MB) |
| allowedTypes | Allowed MIME types | string[] | - |
| allowedSuffixes | Allowed file suffixes (without dot) | string[] | - |
| scenes | Scene-level configuration override | Record<string, SceneConfig> | - |
Scene Configuration (SceneConfig)
Each scene can override global configuration:
scenes: {
avatar: {
maxSize: 2 * 1024 * 1024, // 2MB
allowedTypes: ['image/png', 'image/jpeg'],
allowedSuffixes: ['png', 'jpg', 'jpeg'],
},
video: {
maxSize: 500 * 1024 * 1024, // 500MB
allowedTypes: ['video/mp4'],
allowedSuffixes: ['mp4'],
},
}Complete Configuration Example
import { Module } from '@nestjs/common';
import { UploadModule } from '@vup/nest-upload';
@Module({
imports: [
UploadModule.forRoot({
uploadDir: 'uploads',
baseUrl: 'https://your-domain.com/uploads',
maxSize: 10 * 1024 * 1024, // 10MB
allowedSuffixes: ['png', 'jpg', 'jpeg', 'pdf'],
scenes: {
avatar: {
maxSize: 2 * 1024 * 1024,
allowedTypes: ['image/png', 'image/jpeg'],
},
document: {
maxSize: 5 * 1024 * 1024,
allowedSuffixes: ['pdf', 'doc', 'docx'],
},
video: {
maxSize: 500 * 1024 * 1024,
allowedTypes: ['video/mp4'],
},
},
}),
],
})
export class AppModule {}HTTP Interfaces
Upload File
POST /upload?scene=avatar
Content-Type: multipart/form-data
file: [file]Response Example:
{
"id": "123456",
"filename": "avatar.jpg",
"originalName": "my-avatar.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"url": "https://your-domain.com/uploads/avatar/2024-01-19/avatar.jpg",
"scene": "avatar",
"createdAt": "2024-01-19T10:00:00.000Z"
}Get File List
GET /upload/list?scene=avatar&page=1&limit=10Response Example:
{
"data": [
{
"id": "123456",
"filename": "avatar.jpg",
"url": "https://your-domain.com/uploads/avatar/2024-01-19/avatar.jpg",
"size": 102400,
"createdAt": "2024-01-19T10:00:00.000Z"
}
],
"total": 100,
"page": 1,
"limit": 10
}Get File Details
GET /upload/:idDelete File
DELETE /upload/:idFile Storage Structure
Files are stored in the following structure:
{uploadDir}/{scene}/{YYYY-MM-DD}/{filename}For example:
uploads/
├─ avatar/
│ ├─ 2024-01-19/
│ │ ├─ avatar-001.jpg
│ │ └─ avatar-002.jpg
│ └─ 2024-01-20/
│ └─ avatar-003.jpg
├─ document/
│ └─ 2024-01-19/
│ └─ doc-001.pdf
└─ default/
└─ 2024-01-19/
└─ file-001.pngWhen scene is not provided, the default scene default is used.
Custom Entity
If you need to extend the table structure, you can extend BaseUploadEntity:
import { BaseUploadEntity } from '@vup/nest-upload';
import { Column, Entity } from 'typeorm';
@Entity('custom_upload_files')
export class CustomUploadEntity extends BaseUploadEntity {
@Column({ name: 'biz_type', length: 64 })
bizType!: string;
@Column({ name: 'biz_id', length: 64 })
bizId!: string;
@Column({ name: 'user_id', length: 64, nullable: true })
userId?: string;
}Then specify in UploadModule configuration:
UploadModule.forRoot({
entity: CustomUploadEntity,
uploadDir: 'uploads',
baseUrl: 'http://localhost:3000/uploads',
});Note: TypeORM needs to register this entity in the business project's TypeOrmModule.forRoot.
Using Service
In addition to HTTP interfaces, you can also directly use UploadService:
import { Injectable } from '@nestjs/common';
import { UploadService } from '@vup/nest-upload';
@Injectable()
export class MyService {
constructor(private readonly uploadService: UploadService) {}
async uploadFile(file: Express.Multer.File, scene?: string) {
return await this.uploadService.upload(file, scene);
}
async getFileList(scene?: string, page = 1, limit = 10) {
return await this.uploadService.getList(scene, page, limit);
}
async deleteFile(id: string) {
return await this.uploadService.delete(id);
}
}Frontend Integration Examples
Upload Using FormData
async function uploadFile(file: File, scene?: string) {
const formData = new FormData();
formData.append('file', file);
const url = scene ? `/upload?scene=${scene}` : '/upload';
const response = await fetch(url, {
method: 'POST',
body: formData,
});
return await response.json();
}
// Usage example
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const result = await uploadFile(file, 'avatar');
console.log('Upload successful:', result);
}
});Upload Using axios
import axios from 'axios';
async function uploadFile(file: File, scene?: string) {
const formData = new FormData();
formData.append('file', file);
const { data } = await axios.post(
scene ? `/upload?scene=${scene}` : '/upload',
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
);
return data;
}Notes
- Default uses
LocalStorageAdapter, files stored in local file system - TypeORM needs to be configured to use database functionality
- File size and type restrictions are set during configuration and automatically validated during upload
- It's recommended to configure
baseUrlas the actual domain in production environment - Date format in file paths is
YYYY-MM-DDfor easy date-based file management - If you need to use cloud storage (OSS, S3, etc.), you can implement a custom
StorageAdapter