@nest-util/nest-file
v2.0.0
Published
S3-compatible file management library for NestJS with presigned URL uploads and metadata tracking via TypeORM.
Readme
@nest-util/nest-file
S3-compatible file management library for NestJS with presigned URL uploads and metadata tracking via TypeORM.
Installation
We recommend using pnpm as your package manager.
pnpm add @nest-util/nest-filePeer dependencies:
pnpm add @nestjs/common @nestjs/swagger @nestjs/typeorm class-validator typeormOptional — for RBAC permissions:
pnpm add @nest-util/nest-authFeatures
- S3-compatible presigned URL uploads (AWS S3, MinIO, DigitalOcean Spaces, etc.)
- Secure client-side upload flow: request URL → upload directly → confirm
- TypeORM-based file metadata tracking
- Auto-registered controller via
NestFileModule.forRoot()(opt-in) - Controller factory via
CreateFileController()for custom routing - Optional RBAC permissions integration with
@nest-util/nest-auth - Automatic Swagger documentation
Quick Start
import { NestFileModule } from '@nest-util/nest-file';
@Module({
imports: [
NestFileModule.forRoot({
s3: {
region: process.env.AWS_REGION!,
bucket: process.env.S3_BUCKET!,
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
publicUrl: 'https://my-bucket.s3.amazonaws.com',
},
controller: {
permissions: {
upload: 'files.create',
download: 'files.read',
list: 'files.read',
remove: 'files.delete',
},
},
}),
],
})
export class AppModule {}Endpoints are available immediately — no controller class needed.
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
| s3.region | string | required | AWS region |
| s3.bucket | string | required | S3 bucket name |
| s3.accessKeyId | string | required | S3 access key |
| s3.secretAccessKey | string | required | S3 secret key |
| s3.endpoint | string | — | Custom endpoint (MinIO, DigitalOcean, etc.) |
| s3.forcePathStyle | boolean | false | Path-style URLs (required for MinIO) |
| s3.publicUrl | string | — | Public URL prefix for stored files |
| upload.maxFileSize | number | — | Max upload size in bytes |
| upload.allowedMimeTypes | string[] | — | Allowed MIME types (supports image/* wildcards) |
| upload.pathPrefix | string | 'uploads' | S3 key prefix |
| upload.presignedUrlExpiresIn | number | 3600 | Presigned URL expiry in seconds |
| controller.enable | boolean | true | Auto-register controller |
| controller.path | string | 'files' | Controller route path |
| controller.permissions | object | — | RBAC permissions per endpoint |
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /files/upload-url | Request a presigned upload URL |
| POST | /files/confirm | Confirm upload and process the file |
| GET | /files/:id/download | Get a presigned download URL |
| GET | /files | List all files (paginated) |
| GET | /files/mine | Get current user's files (paginated) |
| GET | /files/:id | Get file metadata |
| DELETE | /files/:id | Delete a file from S3 |
Upload Flow
- Request URL —
POST /files/upload-urlwith{ fileName, mimeType, folder? } - Upload to S3 —
PUTthe file directly to the presigned URL - Confirm —
POST /files/confirmwith{ fileId, key }to finalize
RequestUploadDto accepts an optional folder field that overrides upload.pathPrefix for the S3 key (e.g. avatars/1712345678901-photo.jpg). UpdateFileDto accepts optional description and tags (comma-separated).
Services
FileService
requestUpload(dto: RequestUploadDto, userId: string)→Promise<PresignedUploadResult>(uploadUrl,key,fileId)confirmUpload(dto: ConfirmUploadDto)→Promise<FileEntity>getDownloadUrl(fileId: string)→Promise<string>(presigned download URL)getFile(fileId: string)→Promise<FileEntity>deleteFile(fileId: string)→Promise<boolean>findAll({ page?, limit?, orderBy?, orderDirection? })→Promise<{ data, meta }>findMine(userId, query?)→Promise<{ data, meta }>(user-scoped)
S3Service
generatePresignedUploadUrl({ key, contentType, expiresIn? })→Promise<{ uploadUrl, key }>generatePresignedDownloadUrl(key, expiresIn?)→Promise<string>uploadBuffer(key, buffer, contentType)→Promise<{ key, url }>(server-side)deleteObject(key)→Promise<void>objectExists(key)→Promise<boolean>getClient()/getBucket()— rawS3Client/ bucket accessors
Helpers
generateStoredName(fileName) (sanitize + timestamp), generateS3Key(storedName, pathPrefix?) (default uploads/), isImageMime(mimeType), getMimeTypeExtension(mimeType) (fallback 'bin'), IMAGE_MIME_PREFIXES.
Result & Metadata Interfaces
interface PresignedUploadResult { uploadUrl: string; key: string; fileId: string; }
interface PresignedDownloadResult { downloadUrl: string; }
interface FileMetadata {
originalName: string;
storedName: string;
mimeType: string;
size: number;
bucket: string;
key: string;
url: string;
userId: string;
metadata?: Record<string, unknown>;
}NEST_FILE_OPTIONS is the injection token for the resolved NestFileOptions.
FileEntity
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| originalName | string | Original filename |
| storedName | string | Sanitized stored filename |
| mimeType | string | MIME type |
| size | bigint | File size in bytes |
| bucket | string | S3 bucket name |
| key | string | S3 object key |
| url | string? | Public URL |
| userId | string | Uploader user ID |
| metadata | jsonb? | Custom metadata |
| createdAt | Date | Creation timestamp |
| updatedAt | Date | Last update timestamp |
Custom Controller
If you need custom routing, disable the auto-registered controller and use CreateFileController():
NestFileModule.forRoot({
s3: { /* ... */ },
controller: { enable: false },
});import { Controller } from '@nestjs/common';
import { CreateFileController } from '@nest-util/nest-file';
const FileBase = CreateFileController({
permissions: {
upload: 'files.create',
download: 'files.read',
list: 'files.read',
remove: 'files.delete',
},
});
@Controller('custom-files')
export class FilesController extends FileBase {
constructor(override readonly fileService: FileService) {
super(fileService);
}
}Testing
Use the @nest-util/nest-file/testing entry point for mock factories and generated test suites.
Generated Test Suites
import { fileServiceTests } from '@nest-util/nest-file/testing';
import { FileService } from '@nest-util/nest-file';
import { FileEntity } from './file.entity';
describe('FileService', () => {
fileServiceTests({
serviceClass: FileService,
entity: FileEntity,
test: {
requestUploadPayload: { fileName: 'photo.jpg', mimeType: 'image/jpeg' },
confirmUploadPayload: { fileId: '00000000-0000-0000-0000-000000000001', key: 'uploads/photo.jpg' },
},
});
});Config types: FileServiceTestConfig and FileControllerTestConfig; a FileTestContext exposes { service, repository, s3Service } mocks.
Manual Setup with Mocks
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import {
FileService, FileEntity,
createMockRepository, createMockS3Service,
} from '@nest-util/nest-file/testing';
const module: TestingModule = await Test.createTestingModule({
providers: [
FileService,
{ provide: getRepositoryToken(FileEntity), useValue: createMockRepository() },
{ provide: S3Service, useValue: createMockS3Service() },
],
}).compile();Building
Run nx build nest-file to build the library.
Running unit tests
Run nx test nest-file to execute the unit tests via Jest.
