@ackplus/nest-file-storage 1.1.11 → 1.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +658 -6
  3. package/{src → dist}/index.d.ts +0 -1
  4. package/dist/index.js +21 -0
  5. package/dist/index.js.map +1 -0
  6. package/{src → dist}/lib/constants.d.ts +0 -1
  7. package/{src → dist}/lib/constants.js +1 -0
  8. package/dist/lib/constants.js.map +1 -0
  9. package/{src → dist}/lib/file-storage.service.d.ts +0 -1
  10. package/{src → dist}/lib/file-storage.service.js +3 -3
  11. package/dist/lib/file-storage.service.js.map +1 -0
  12. package/{src → dist}/lib/index.d.ts +0 -1
  13. package/dist/lib/index.js +22 -0
  14. package/dist/lib/index.js.map +1 -0
  15. package/{src → dist}/lib/interceptor/file-storage.interceptor.d.ts +0 -4
  16. package/{src → dist}/lib/interceptor/file-storage.interceptor.js +5 -17
  17. package/dist/lib/interceptor/file-storage.interceptor.js.map +1 -0
  18. package/{src → dist}/lib/nest-file-storage.module.d.ts +0 -1
  19. package/{src → dist}/lib/nest-file-storage.module.js +9 -3
  20. package/dist/lib/nest-file-storage.module.js.map +1 -0
  21. package/{src → dist}/lib/storage/azure.storage.d.ts +0 -1
  22. package/{src → dist}/lib/storage/azure.storage.js +49 -10
  23. package/dist/lib/storage/azure.storage.js.map +1 -0
  24. package/{src → dist}/lib/storage/local.storage.d.ts +0 -15
  25. package/{src → dist}/lib/storage/local.storage.js +44 -32
  26. package/dist/lib/storage/local.storage.js.map +1 -0
  27. package/{src → dist}/lib/storage/s3.storage.d.ts +0 -1
  28. package/{src → dist}/lib/storage/s3.storage.js +43 -8
  29. package/dist/lib/storage/s3.storage.js.map +1 -0
  30. package/{src → dist}/lib/storage.factory.d.ts +0 -1
  31. package/dist/lib/storage.factory.js +46 -0
  32. package/dist/lib/storage.factory.js.map +1 -0
  33. package/{src → dist}/lib/types.d.ts +0 -14
  34. package/{src → dist}/lib/types.js +1 -0
  35. package/dist/lib/types.js.map +1 -0
  36. package/dist/tsconfig.build.tsbuildinfo +1 -0
  37. package/examples/1-basic-local-storage.example.ts +22 -0
  38. package/examples/10-testing.example.ts +233 -0
  39. package/examples/2-s3-storage.example.ts +40 -0
  40. package/examples/3-azure-storage.example.ts +35 -0
  41. package/examples/4-upload-controller.example.ts +117 -0
  42. package/examples/5-custom-configuration.example.ts +75 -0
  43. package/examples/6-file-service.example.ts +124 -0
  44. package/examples/7-user-avatar.example.ts +139 -0
  45. package/examples/8-document-management.example.ts +265 -0
  46. package/examples/9-dynamic-storage.example.ts +175 -0
  47. package/examples/README.md +122 -0
  48. package/package.json +86 -6
  49. package/src/index.d.ts.map +0 -1
  50. package/src/index.js +0 -7
  51. package/src/lib/constants.d.ts.map +0 -1
  52. package/src/lib/file-storage.service.d.ts.map +0 -1
  53. package/src/lib/index.d.ts.map +0 -1
  54. package/src/lib/index.js +0 -8
  55. package/src/lib/interceptor/file-storage.interceptor.d.ts.map +0 -1
  56. package/src/lib/nest-file-storage.module.d.ts.map +0 -1
  57. package/src/lib/storage/azure.storage.d.ts.map +0 -1
  58. package/src/lib/storage/local.storage.d.ts.map +0 -1
  59. package/src/lib/storage/s3.storage.d.ts.map +0 -1
  60. package/src/lib/storage.factory.d.ts.map +0 -1
  61. package/src/lib/storage.factory.js +0 -81
  62. package/src/lib/types.d.ts.map +0 -1
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Example 7: User Avatar Upload
3
+ *
4
+ * This example demonstrates a complete user avatar upload feature with
5
+ * validation, old avatar cleanup, and database update.
6
+ */
7
+
8
+ import {
9
+ Controller,
10
+ Post,
11
+ UseInterceptors,
12
+ Body,
13
+ Request,
14
+ BadRequestException,
15
+ UseGuards,
16
+ } from '@nestjs/common';
17
+ import { FileStorageInterceptor, FileStorageService } from '@ackplus/nest-file-storage';
18
+ import { JwtAuthGuard } from './auth/jwt-auth.guard'; // Your auth guard
19
+
20
+ // User entity interface
21
+ interface User {
22
+ id: number;
23
+ email: string;
24
+ avatarKey?: string;
25
+ avatarUrl?: string;
26
+ }
27
+
28
+ // User service (example)
29
+ class UserService {
30
+ async findById(id: number): Promise<User> {
31
+ // Your database query
32
+ return {} as User;
33
+ }
34
+
35
+ async updateAvatar(id: number, avatarKey: string, avatarUrl: string): Promise<User> {
36
+ // Your database update
37
+ return {} as User;
38
+ }
39
+ }
40
+
41
+ @Controller('users')
42
+ export class UserAvatarController {
43
+ constructor(private readonly userService: UserService) {}
44
+
45
+ @Post('avatar')
46
+ @UseGuards(JwtAuthGuard) // Require authentication
47
+ @UseInterceptors(
48
+ FileStorageInterceptor('avatar', {
49
+ fileName: (file, req) => {
50
+ // Validate file type
51
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
52
+ if (!allowedTypes.includes(file.mimetype)) {
53
+ throw new BadRequestException(
54
+ 'Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed.'
55
+ );
56
+ }
57
+
58
+ // Validate file size (5MB max)
59
+ const maxSize = 5 * 1024 * 1024; // 5MB
60
+ if (file.size > maxSize) {
61
+ throw new BadRequestException('File size must be less than 5MB');
62
+ }
63
+
64
+ // Generate filename: avatar-{userId}-{timestamp}.{ext}
65
+ const userId = req.user.id;
66
+ const timestamp = Date.now();
67
+ const ext = file.originalname.split('.').pop();
68
+ return `avatar-${userId}-${timestamp}.${ext}`;
69
+ },
70
+
71
+ fileDist: () => 'avatars', // Store in avatars directory
72
+
73
+ // Return full file object
74
+ mapToRequestBody: (file) => file,
75
+ })
76
+ )
77
+ async uploadAvatar(@Body() body: any, @Request() req) {
78
+ const userId = req.user.id;
79
+ const newAvatar = body.avatar;
80
+
81
+ // Get current user
82
+ const user = await this.userService.findById(userId);
83
+
84
+ // Delete old avatar if exists
85
+ if (user.avatarKey) {
86
+ try {
87
+ const storage = await FileStorageService.getStorage();
88
+ await storage.deleteFile(user.avatarKey);
89
+ } catch (error) {
90
+ // Log error but don't fail the upload
91
+ console.error('Failed to delete old avatar:', error);
92
+ }
93
+ }
94
+
95
+ // Update user with new avatar
96
+ const updatedUser = await this.userService.updateAvatar(
97
+ userId,
98
+ newAvatar.key,
99
+ newAvatar.url
100
+ );
101
+
102
+ return {
103
+ message: 'Avatar updated successfully',
104
+ avatar: {
105
+ key: newAvatar.key,
106
+ url: newAvatar.url,
107
+ size: newAvatar.size,
108
+ },
109
+ user: {
110
+ id: updatedUser.id,
111
+ email: updatedUser.email,
112
+ avatarUrl: updatedUser.avatarUrl,
113
+ },
114
+ };
115
+ }
116
+
117
+ @Post('avatar/delete')
118
+ @UseGuards(JwtAuthGuard)
119
+ async deleteAvatar(@Request() req) {
120
+ const userId = req.user.id;
121
+ const user = await this.userService.findById(userId);
122
+
123
+ if (!user.avatarKey) {
124
+ throw new BadRequestException('No avatar to delete');
125
+ }
126
+
127
+ // Delete from storage
128
+ const storage = await FileStorageService.getStorage();
129
+ await storage.deleteFile(user.avatarKey);
130
+
131
+ // Update database
132
+ await this.userService.updateAvatar(userId, null, null);
133
+
134
+ return {
135
+ message: 'Avatar deleted successfully',
136
+ };
137
+ }
138
+ }
139
+
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Example 8: Document Management System
3
+ *
4
+ * This example demonstrates a complete document management system with
5
+ * upload, download, listing, and deletion features.
6
+ */
7
+
8
+ import {
9
+ Controller,
10
+ Get,
11
+ Post,
12
+ Delete,
13
+ Param,
14
+ UseInterceptors,
15
+ Body,
16
+ Res,
17
+ NotFoundException,
18
+ StreamableFile,
19
+ } from '@nestjs/common';
20
+ import { Response } from 'express';
21
+ import { FileStorageInterceptor, FileStorageService } from '@ackplus/nest-file-storage';
22
+
23
+ // Document entity interface
24
+ interface Document {
25
+ id: number;
26
+ name: string;
27
+ key: string;
28
+ url: string;
29
+ size: number;
30
+ mimetype: string;
31
+ uploadedAt: Date;
32
+ }
33
+
34
+ // Document service (example)
35
+ class DocumentService {
36
+ async create(data: Partial<Document>): Promise<Document> {
37
+ // Save to database
38
+ return {} as Document;
39
+ }
40
+
41
+ async findAll(): Promise<Document[]> {
42
+ // Get all documents from database
43
+ return [] as Document[];
44
+ }
45
+
46
+ async findById(id: number): Promise<Document> {
47
+ // Get document from database
48
+ return {} as Document;
49
+ }
50
+
51
+ async delete(id: number): Promise<void> {
52
+ // Delete from database
53
+ }
54
+ }
55
+
56
+ @Controller('documents')
57
+ export class DocumentController {
58
+ constructor(private readonly documentService: DocumentService) {}
59
+
60
+ /**
61
+ * Upload single document
62
+ */
63
+ @Post('upload')
64
+ @UseInterceptors(
65
+ FileStorageInterceptor('document', {
66
+ fileDist: () => 'documents',
67
+ mapToRequestBody: (file) => file, // Return full file object
68
+ })
69
+ )
70
+ async uploadDocument(@Body() body: any) {
71
+ const uploadedFile = body.document;
72
+
73
+ // Save document info to database
74
+ const document = await this.documentService.create({
75
+ name: uploadedFile.originalName,
76
+ key: uploadedFile.key,
77
+ url: uploadedFile.url,
78
+ size: uploadedFile.size,
79
+ mimetype: uploadedFile.mimetype,
80
+ uploadedAt: new Date(),
81
+ });
82
+
83
+ return {
84
+ message: 'Document uploaded successfully',
85
+ document,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Upload multiple documents
91
+ */
92
+ @Post('upload/multiple')
93
+ @UseInterceptors(
94
+ FileStorageInterceptor({
95
+ type: 'array',
96
+ fieldName: 'documents',
97
+ maxCount: 10,
98
+ }, {
99
+ fileDist: () => 'documents',
100
+ mapToRequestBody: (files) => files,
101
+ })
102
+ )
103
+ async uploadMultipleDocuments(@Body() body: any) {
104
+ const uploadedFiles = body.documents;
105
+
106
+ // Save all documents to database
107
+ const documents = await Promise.all(
108
+ uploadedFiles.map(file =>
109
+ this.documentService.create({
110
+ name: file.originalName,
111
+ key: file.key,
112
+ url: file.url,
113
+ size: file.size,
114
+ mimetype: file.mimetype,
115
+ uploadedAt: new Date(),
116
+ })
117
+ )
118
+ );
119
+
120
+ return {
121
+ message: `${documents.length} documents uploaded successfully`,
122
+ documents,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Get all documents
128
+ */
129
+ @Get()
130
+ async getAllDocuments() {
131
+ const documents = await this.documentService.findAll();
132
+ return { documents };
133
+ }
134
+
135
+ /**
136
+ * Download document
137
+ */
138
+ @Get(':id/download')
139
+ async downloadDocument(
140
+ @Param('id') id: number,
141
+ @Res({ passthrough: true }) res: Response,
142
+ ): Promise<StreamableFile> {
143
+ // Get document from database
144
+ const document = await this.documentService.findById(id);
145
+ if (!document) {
146
+ throw new NotFoundException('Document not found');
147
+ }
148
+
149
+ // Get file from storage
150
+ const storage = await FileStorageService.getStorage();
151
+ const fileBuffer = await storage.getFile(document.key);
152
+
153
+ // Set response headers
154
+ res.set({
155
+ 'Content-Type': document.mimetype || 'application/octet-stream',
156
+ 'Content-Disposition': `attachment; filename="${document.name}"`,
157
+ 'Content-Length': document.size,
158
+ });
159
+
160
+ return new StreamableFile(fileBuffer);
161
+ }
162
+
163
+ /**
164
+ * Get document URL (for preview or direct access)
165
+ */
166
+ @Get(':id/url')
167
+ async getDocumentUrl(@Param('id') id: number) {
168
+ const document = await this.documentService.findById(id);
169
+ if (!document) {
170
+ throw new NotFoundException('Document not found');
171
+ }
172
+
173
+ return {
174
+ url: document.url,
175
+ expiresIn: null, // Permanent URL
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Get signed URL (for S3, temporary access)
181
+ */
182
+ @Get(':id/signed-url')
183
+ async getSignedUrl(@Param('id') id: number) {
184
+ const document = await this.documentService.findById(id);
185
+ if (!document) {
186
+ throw new NotFoundException('Document not found');
187
+ }
188
+
189
+ const storage = await FileStorageService.getStorage();
190
+
191
+ let url: string;
192
+ if ('getSignedUrl' in storage) {
193
+ // Generate signed URL (valid for 1 hour)
194
+ url = await storage.getSignedUrl(document.key, { expiresIn: 3600 });
195
+ } else {
196
+ // Fallback to regular URL
197
+ url = storage.getUrl(document.key);
198
+ }
199
+
200
+ return {
201
+ url,
202
+ expiresIn: 3600, // seconds
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Delete document
208
+ */
209
+ @Delete(':id')
210
+ async deleteDocument(@Param('id') id: number) {
211
+ const document = await this.documentService.findById(id);
212
+ if (!document) {
213
+ throw new NotFoundException('Document not found');
214
+ }
215
+
216
+ // Delete from storage
217
+ const storage = await FileStorageService.getStorage();
218
+ await storage.deleteFile(document.key);
219
+
220
+ // Delete from database
221
+ await this.documentService.delete(id);
222
+
223
+ return {
224
+ message: 'Document deleted successfully',
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Copy document
230
+ */
231
+ @Post(':id/copy')
232
+ async copyDocument(@Param('id') id: number) {
233
+ const document = await this.documentService.findById(id);
234
+ if (!document) {
235
+ throw new NotFoundException('Document not found');
236
+ }
237
+
238
+ // Generate new key for the copy
239
+ const timestamp = Date.now();
240
+ const newKey = document.key.replace(
241
+ /(\.[^.]+)$/,
242
+ `-copy-${timestamp}$1`
243
+ );
244
+
245
+ // Copy file in storage
246
+ const storage = await FileStorageService.getStorage();
247
+ const copiedFile = await storage.copyFile(document.key, newKey);
248
+
249
+ // Save copy to database
250
+ const copiedDocument = await this.documentService.create({
251
+ name: `${document.name} (Copy)`,
252
+ key: copiedFile.key,
253
+ url: copiedFile.url,
254
+ size: copiedFile.size,
255
+ mimetype: document.mimetype,
256
+ uploadedAt: new Date(),
257
+ });
258
+
259
+ return {
260
+ message: 'Document copied successfully',
261
+ document: copiedDocument,
262
+ };
263
+ }
264
+ }
265
+
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Example 9: Dynamic Storage Selection
3
+ *
4
+ * This example demonstrates how to dynamically choose storage provider
5
+ * based on request, user preferences, or business logic.
6
+ */
7
+
8
+ import {
9
+ Controller,
10
+ Post,
11
+ UseInterceptors,
12
+ Body,
13
+ Query,
14
+ } from '@nestjs/common';
15
+ import {
16
+ FileStorageInterceptor,
17
+ FileStorageEnum,
18
+ } from '@ackplus/nest-file-storage';
19
+
20
+ @Controller('upload')
21
+ export class DynamicStorageController {
22
+ /**
23
+ * Upload to specific storage based on query parameter
24
+ * Example: POST /upload/dynamic?storage=s3
25
+ */
26
+ @Post('dynamic')
27
+ @UseInterceptors(
28
+ FileStorageInterceptor('file', {
29
+ // Storage type will be determined at runtime
30
+ })
31
+ )
32
+ uploadDynamic(
33
+ @Body() body: any,
34
+ @Query('storage') storage: string,
35
+ ) {
36
+ return {
37
+ message: 'File uploaded successfully',
38
+ storage,
39
+ fileKey: body.file,
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Upload large files to S3, small files to local storage
45
+ */
46
+ @Post('smart')
47
+ @UseInterceptors(
48
+ FileStorageInterceptor('file', {
49
+ fileName: (file, req) => {
50
+ const timestamp = Date.now();
51
+ const ext = file.originalname.split('.').pop();
52
+ return `${timestamp}-${file.originalname}`;
53
+ },
54
+ // Determine storage based on file size
55
+ storageType: FileStorageEnum.LOCAL, // Can be overridden
56
+ })
57
+ )
58
+ uploadSmart(@Body() body: any) {
59
+ return {
60
+ message: 'File uploaded successfully',
61
+ fileKey: body.file,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Upload to S3 explicitly (override default storage)
67
+ */
68
+ @Post('to-s3')
69
+ @UseInterceptors(
70
+ FileStorageInterceptor('file', {
71
+ storageType: FileStorageEnum.S3,
72
+ })
73
+ )
74
+ uploadToS3(@Body() body: any) {
75
+ return {
76
+ message: 'File uploaded to S3',
77
+ fileKey: body.file,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Upload to Azure explicitly
83
+ */
84
+ @Post('to-azure')
85
+ @UseInterceptors(
86
+ FileStorageInterceptor('file', {
87
+ storageType: FileStorageEnum.AZURE,
88
+ })
89
+ )
90
+ uploadToAzure(@Body() body: any) {
91
+ return {
92
+ message: 'File uploaded to Azure',
93
+ fileKey: body.file,
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Upload to local storage explicitly
99
+ */
100
+ @Post('to-local')
101
+ @UseInterceptors(
102
+ FileStorageInterceptor('file', {
103
+ storageType: FileStorageEnum.LOCAL,
104
+ })
105
+ )
106
+ uploadToLocal(@Body() body: any) {
107
+ return {
108
+ message: 'File uploaded to local storage',
109
+ fileKey: body.file,
110
+ };
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Advanced: Custom logic for storage selection
116
+ */
117
+ import { Injectable } from '@nestjs/common';
118
+ import { FileStorageService } from '@ackplus/nest-file-storage';
119
+
120
+ @Injectable()
121
+ export class SmartStorageService {
122
+ /**
123
+ * Select storage based on file size
124
+ */
125
+ async uploadFile(buffer: Buffer, fileName: string, fileSize: number) {
126
+ // Use local storage for small files (< 10MB)
127
+ // Use S3 for large files
128
+ const storageType = fileSize < 10 * 1024 * 1024
129
+ ? FileStorageEnum.LOCAL
130
+ : FileStorageEnum.S3;
131
+
132
+ const storage = await FileStorageService.getStorage(storageType);
133
+ return await storage.putFile(buffer, fileName);
134
+ }
135
+
136
+ /**
137
+ * Select storage based on file type
138
+ */
139
+ async uploadByType(buffer: Buffer, fileName: string, mimetype: string) {
140
+ let storageType: FileStorageEnum;
141
+
142
+ if (mimetype.startsWith('image/')) {
143
+ // Store images on S3 with CloudFront CDN
144
+ storageType = FileStorageEnum.S3;
145
+ } else if (mimetype.startsWith('video/')) {
146
+ // Store videos on Azure
147
+ storageType = FileStorageEnum.AZURE;
148
+ } else {
149
+ // Store other files locally
150
+ storageType = FileStorageEnum.LOCAL;
151
+ }
152
+
153
+ const storage = await FileStorageService.getStorage(storageType);
154
+ return await storage.putFile(buffer, fileName);
155
+ }
156
+
157
+ /**
158
+ * Select storage based on user plan
159
+ */
160
+ async uploadForUser(
161
+ buffer: Buffer,
162
+ fileName: string,
163
+ userPlan: 'free' | 'premium'
164
+ ) {
165
+ // Free users: local storage
166
+ // Premium users: S3 with better performance
167
+ const storageType = userPlan === 'premium'
168
+ ? FileStorageEnum.S3
169
+ : FileStorageEnum.LOCAL;
170
+
171
+ const storage = await FileStorageService.getStorage(storageType);
172
+ return await storage.putFile(buffer, fileName);
173
+ }
174
+ }
175
+
@@ -0,0 +1,122 @@
1
+ # Examples
2
+
3
+ This directory contains comprehensive examples demonstrating various features and use cases of `@ackplus/nest-file-storage`.
4
+
5
+ ## 📚 Available Examples
6
+
7
+ ### Getting Started
8
+
9
+ 1. **[Basic Local Storage](./1-basic-local-storage.example.ts)**
10
+ - Setting up local file storage
11
+ - Basic configuration
12
+ - Perfect for development
13
+
14
+ 2. **[AWS S3 Storage](./2-s3-storage.example.ts)**
15
+ - Configure AWS S3
16
+ - Async configuration with ConfigService
17
+ - Environment variables setup
18
+
19
+ 3. **[Azure Blob Storage](./3-azure-storage.example.ts)**
20
+ - Configure Azure Blob Storage
21
+ - Connection string setup
22
+ - Container management
23
+
24
+ ### Core Features
25
+
26
+ 4. **[File Upload Controller](./4-upload-controller.example.ts)**
27
+ - Single file upload
28
+ - Multiple files upload
29
+ - Multiple fields upload
30
+ - File validation
31
+ - Custom file mapping
32
+
33
+ 5. **[Custom Configuration](./5-custom-configuration.example.ts)**
34
+ - Custom file naming
35
+ - Custom directory structure
36
+ - File transformations
37
+ - Organized file storage
38
+
39
+ 6. **[File Service](./6-file-service.example.ts)**
40
+ - File operations (get, delete, copy)
41
+ - Upload from local filesystem
42
+ - Get public URLs
43
+ - Generate signed URLs (S3)
44
+ - Batch operations
45
+
46
+ ### Real-World Use Cases
47
+
48
+ 7. **[User Avatar Upload](./7-user-avatar.example.ts)**
49
+ - Complete avatar upload feature
50
+ - File validation
51
+ - Old file cleanup
52
+ - Database integration
53
+
54
+ 8. **[Document Management System](./8-document-management.example.ts)**
55
+ - Upload/download documents
56
+ - Document listing
57
+ - File copying
58
+ - Signed URLs for secure access
59
+
60
+ 9. **[Dynamic Storage Selection](./9-dynamic-storage.example.ts)**
61
+ - Switch storage per request
62
+ - Storage selection by file size
63
+ - Storage selection by file type
64
+ - Storage selection by user plan
65
+
66
+ 10. **[Testing](./10-testing.example.ts)**
67
+ - E2E testing examples
68
+ - Unit testing
69
+ - Mock storage implementation
70
+ - Test setup and cleanup
71
+
72
+ ## 🚀 How to Use These Examples
73
+
74
+ ### 1. Copy Example Code
75
+
76
+ Simply copy the relevant example code to your project:
77
+
78
+ ```bash
79
+ # Copy specific example
80
+ cp examples/1-basic-local-storage.example.ts src/config/storage.config.ts
81
+ ```
82
+
83
+ ### 2. Modify for Your Needs
84
+
85
+ Each example is well-documented with comments explaining:
86
+ - What the code does
87
+ - Configuration options
88
+ - Environment variables needed
89
+ - Expected behavior
90
+
91
+ ### 3. Run Your Application
92
+
93
+ ```bash
94
+ npm run start:dev
95
+ ```
96
+
97
+ ## 💡 Tips
98
+
99
+ - **Start Simple**: Begin with Example 1 (Local Storage) for development
100
+ - **Environment Variables**: Use Example 2 or 3 for production with proper secrets management
101
+ - **Validation**: See Example 4 for file type and size validation
102
+ - **Custom Logic**: Examples 5-9 show advanced customization options
103
+ - **Testing**: Example 10 provides complete testing setup
104
+
105
+ ## 📖 More Resources
106
+
107
+ - **[Main Documentation](../README.md)** - Complete feature guide
108
+ - **[GitHub Issues](https://github.com/ack-solutions/nest-file-storage/issues)** - Report issues or request features
109
+ - **[NPM Package](https://www.npmjs.com/package/@ackplus/nest-file-storage)** - Published package
110
+
111
+ ## 🤝 Contributing
112
+
113
+ Found a bug in an example? Want to add a new example? PRs are welcome!
114
+
115
+ 1. Add your example file: `[number]-[name].example.ts`
116
+ 2. Add it to this README
117
+ 3. Submit a PR
118
+
119
+ ---
120
+
121
+ **Need help?** Open an issue on [GitHub](https://github.com/ack-solutions/nest-file-storage/issues)
122
+