File Uploads

MoroJS provides basic file upload handling. For advanced features like cloud storage and file processing, integrate with middleware libraries like Multer and cloud SDKs.

Basic File Upload

MoroJS provides built-in support for file uploads with validation, type checking, and security features.

File Upload with Multer Integration

typescript

1import { createApp } from '@morojs/moro';
2import multer from 'multer';
3import path from 'path';
4
5const app = createApp({
6  cors: true,
7  compression: true
8});
9
10// Configure multer for file uploads
11const storage = multer.diskStorage({
12  destination: (req, file, cb) => {
13    cb(null, './uploads/');
14  },
15  filename: (req, file, cb) => {
16    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
17    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
18  }
19});
20
21const upload = multer({
22  storage: storage,
23  limits: {
24    fileSize: 10 * 1024 * 1024 // 10MB limit
25  },
26  fileFilter: (req, file, cb) => {
27    // Allow only specific file types
28    const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
29    if (allowedTypes.includes(file.mimetype)) {
30      cb(null, true);
31    } else {
32      cb(new Error('Invalid file type'), false);
33    }
34  }
35});
36
37// File upload endpoint
38app.post('/upload', upload.single('file'), (req, res) => {
39  if (!req.file) {
40    res.status(400);
41    return { success: false, error: 'No file uploaded' };
42  }
43  
44  return {
45    success: true,
46    file: {
47      filename: req.file.filename,
48      originalname: req.file.originalname,
49      mimetype: req.file.mimetype,
50      size: req.file.size,
51      path: req.file.path
52    }
53  };
54});
55
56// Multiple file upload
57app.post('/upload/multiple', upload.array('files', 5), (req, res) => {
58  if (!req.files || req.files.length === 0) {
59    res.status(400);
60    return { success: false, error: 'No files uploaded' };
61  }
62  
63  const files = req.files.map(file => ({
64    filename: file.filename,
65    originalname: file.originalname,
66    mimetype: file.mimetype,
67    size: file.size,
68    path: file.path
69  }));
70  
71  return {
72    success: true,
73    files,
74    count: files.length
75  };
76});

Automatic Features

File uploads in MoroJS include automatic:

  • • File type validation based on MIME types
  • • Size limit enforcement
  • • Malware scanning (configurable)
  • • Temporary file cleanup
  • • Progress tracking for large files

Multiple File Uploads

Multiple Files Handler

typescript

1// Multiple files schema
2const MultipleFilesSchema = z.object({
3  files: z.array(z.custom<File>((val) => val instanceof File))
4    .min(1, 'At least one file is required')
5    .max(5, 'Maximum 5 files allowed'),
6  category: z.enum(['documents', 'images', 'videos'])
7});
8
9app.post('/upload/multiple', {
10  body: MultipleFilesSchema,
11  handler: async ({ body }) => {
12    const { files, category } = body;
13    
14    const results = await Promise.all(
15      files.map(async (file, index) => {
16        try {
17          const savedPath = await saveFile(file, {
18            directory: `uploads/${category}`,
19            rename: `${Date.now()}_${index}_${file.name}`
20          });
21          
22          return {
23            success: true,
24            originalName: file.name,
25            savedPath,
26            size: file.size,
27            type: file.type
28          };
29        } catch (error) {
30          return {
31            success: false,
32            originalName: file.name,
33            error: error.message
34          };
35        }
36      })
37    );
38    
39    const successful = results.filter(r => r.success);
40    const failed = results.filter(r => !r.success);
41    
42    return {
43      uploaded: successful.length,
44      failed: failed.length,
45      results
46    };
47  }
48});

Advanced Configuration

Advanced File Upload Configuration

typescript

1const app = createApp({
2  features: {
3    fileUploads: {
4      enabled: true,
5      
6      // Size limits
7      maxSize: '50MB',
8      maxFiles: 10,
9      
10      // File type restrictions
11      allowedTypes: [
12        'image/jpeg',
13        'image/png',
14        'image/gif',
15        'application/pdf',
16        'text/plain',
17        'application/json'
18      ],
19      
20      // Storage configuration
21      storage: {
22        type: 'local', // 'local' | 's3' | 'gcs' | 'azure'
23        destination: './uploads',
24        
25        // File naming strategy
26        filename: (file, context) => {
27          const timestamp = Date.now();
28          const userId = context.get('userId') || 'anonymous';
29          const ext = path.extname(file.name);
30          return `${userId}_${timestamp}${ext}`;
31        },
32        
33        // Directory structure
34        directory: (file, context) => {
35          const date = new Date().toISOString().split('T')[0];
36          const type = file.type.split('/')[0]; // 'image', 'application', etc.
37          return `uploads/${date}/${type}`;
38        }
39      },
40      
41      // Security settings
42      security: {
43        scanForMalware: true,
44        quarantineOnThreat: true,
45        
46        // Content validation
47        validateContent: true,
48        allowExecutables: false,
49        
50        // Image-specific security
51        stripMetadata: true,
52        resizeImages: {
53          maxWidth: 2048,
54          maxHeight: 2048,
55          quality: 85
56        }
57      },
58      
59      // Processing pipeline
60      processing: {
61        // Image processing
62        images: {
63          generateThumbnails: true,
64          thumbnailSizes: [150, 300, 600],
65          formats: ['webp', 'jpeg'] // Convert to multiple formats
66        },
67        
68        // Document processing
69        documents: {
70          extractText: true,
71          generatePreview: true
72        }
73      }
74    }
75  }
76});

Cloud Storage Integration

AWS S3 Configuration

typescript

1import { S3StorageAdapter } from '@morojs/moro/storage';
2
3const app = createApp({
4  features: {
5    fileUploads: {
6      enabled: true,
7      storage: new S3StorageAdapter({
8        bucket: process.env.S3_BUCKET,
9        region: process.env.S3_REGION,
10        accessKeyId: process.env.S3_ACCESS_KEY,
11        secretAccessKey: process.env.S3_SECRET_KEY,
12        
13        // Optional: Custom S3 configuration
14        acl: 'private',
15        serverSideEncryption: 'AES256',
16        
17        // CDN integration
18        cdnUrl: process.env.CLOUDFRONT_URL,
19        
20        // File organization
21        keyPrefix: (file, context) => {
22          const userId = context.get('userId');
23          const date = new Date().toISOString().split('T')[0];
24          return `users/${userId}/${date}/`;
25        }
26      })
27    }
28  }
29});
30
31// Upload with cloud storage
32app.post('/upload/cloud', {
33  body: z.object({
34    file: z.custom<File>((val) => val instanceof File),
35    public: z.boolean().default(false)
36  }),
37  handler: async ({ body }) => {
38    const { file, public: isPublic } = body;
39    
40    const result = await uploadToCloud(file, {
41      acl: isPublic ? 'public-read' : 'private',
42      metadata: {
43        uploadedAt: new Date().toISOString(),
44        originalName: file.name
45      }
46    });
47    
48    return {
49      success: true,
50      url: result.url,
51      key: result.key,
52      public: isPublic
53    };
54  }
55});

Google Cloud Storage Configuration

typescript

1import { GCSStorageAdapter } from '@morojs/moro/storage';
2
3const app = createApp({
4  features: {
5    fileUploads: {
6      storage: new GCSStorageAdapter({
7        projectId: process.env.GCP_PROJECT_ID,
8        keyFilename: process.env.GCP_KEY_FILE,
9        bucket: process.env.GCS_BUCKET,
10        
11        // Optional configuration
12        uniformBucketLevelAccess: true,
13        
14        // Lifecycle management
15        lifecycle: {
16          deleteAfter: 365, // days
17          archiveAfter: 90   // days
18        }
19      })
20    }
21  }
22});

File Processing

Image Processing Pipeline

typescript

1import { processImage } from '@morojs/moro/processing';
2
3app.post('/upload/image', {
4  body: z.object({
5    image: z.custom<File>((val) => val instanceof File && val.type.startsWith('image/')),
6    options: z.object({
7      generateThumbnails: z.boolean().default(true),
8      watermark: z.boolean().default(false),
9      optimize: z.boolean().default(true)
10    }).optional()
11  }),
12  handler: async ({ body }) => {
13    const { image, options = {} } = body;
14    
15    // Process the image
16    const processed = await processImage(image, {
17      // Resize options
18      resize: {
19        maxWidth: 1920,
20        maxHeight: 1080,
21        fit: 'inside',
22        withoutEnlargement: true
23      },
24      
25      // Quality optimization
26      optimize: options.optimize ? {
27        quality: 85,
28        progressive: true,
29        mozjpeg: true
30      } : false,
31      
32      // Generate multiple sizes
33      variants: options.generateThumbnails ? [
34        { name: 'thumbnail', width: 150, height: 150, fit: 'cover' },
35        { name: 'medium', width: 600, height: 400, fit: 'inside' },
36        { name: 'large', width: 1200, height: 800, fit: 'inside' }
37      ] : [],
38      
39      // Watermark
40      watermark: options.watermark ? {
41        image: './assets/watermark.png',
42        position: 'bottom-right',
43        opacity: 0.5
44      } : null,
45      
46      // Format conversion
47      formats: ['webp', 'jpeg'],
48      
49      // Metadata
50      stripMetadata: true
51    });
52    
53    return {
54      success: true,
55      original: processed.original,
56      variants: processed.variants,
57      totalSize: processed.totalSize,
58      formats: processed.formats
59    };
60  }
61});

Document Processing

typescript

1app.post('/upload/document', {
2  body: z.object({
3    document: z.custom<File>((val) => 
4      val instanceof File && 
5      ['application/pdf', 'text/plain', 'application/msword'].includes(val.type)
6    ),
7    extractText: z.boolean().default(true),
8    generatePreview: z.boolean().default(true)
9  }),
10  handler: async ({ body }) => {
11    const { document, extractText, generatePreview } = body;
12    
13    const result = {
14      filename: document.name,
15      size: document.size,
16      type: document.type
17    };
18    
19    // Text extraction
20    if (extractText) {
21      result.text = await extractTextFromDocument(document);
22      result.wordCount = result.text.split(/\s+/).length;
23    }
24    
25    // Preview generation
26    if (generatePreview) {
27      result.preview = await generateDocumentPreview(document, {
28        pages: 1, // First page only
29        format: 'jpeg',
30        width: 600,
31        height: 800
32      });
33    }
34    
35    // Save to storage
36    const savedPath = await saveFile(document);
37    
38    return {
39      success: true,
40      path: savedPath,
41      ...result
42    };
43  }
44});

Security Best Practices

Security Features

  • • Automatic malware scanning
  • • File type validation (MIME + extension)
  • • Content-based validation
  • • Size limit enforcement
  • • Metadata stripping
  • • Quarantine system

Security Considerations

  • • Never trust client-side validation
  • • Scan all uploads for malware
  • • Store uploads outside web root
  • • Use CDN for public file serving
  • • Implement rate limiting
  • • Log all upload activities

Security Configuration Example

typescript

1const app = createApp({
2  features: {
3    fileUploads: {
4      security: {
5        // Malware scanning
6        scanForMalware: true,
7        quarantineOnThreat: true,
8        
9        // File validation
10        validateContent: true,
11        allowExecutables: false,
12        maxFileNameLength: 255,
13        
14        // Forbidden patterns
15        forbiddenExtensions: ['.exe', '.bat', '.cmd', '.scr', '.vbs'],
16        forbiddenMimeTypes: ['application/x-executable'],
17        
18        // Content analysis
19        analyzeContent: {
20          detectMaliciousScripts: true,
21          validateImageHeaders: true,
22          checkForEmbeddedFiles: true
23        }
24      }
25    }
26  }
27});
28
29// Rate limiting for uploads
30app.post('/upload/*', {
31  middleware: [
32    rateLimit({
33      max: 5, // 5 uploads per minute
34      window: '1m',
35      keyGenerator: (context) => context.ip
36    })
37  ]
38});

Next Steps