Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

File Uploads

MoroJS provides built-in file upload middleware with validation, type checking, and security features. Easily handle single or multiple file uploads with minimal configuration.

File Uploads That Just Work

Handle file uploads with validation, security, and processing.
Local storage, cloud storage, and image processing included.

It's This Simple

Built-in File Upload Middleware

typescript

1import { createApp, builtInMiddleware } from '@morojs/moro';
2
3const app = createApp();
4
5// Built-in upload middleware
6app.use(builtInMiddleware.upload({
7  dest: './uploads',
8  maxFileSize: 10 * 1024 * 1024, // 10MB
9  maxFiles: 5,
10  allowedTypes: ['image/jpeg', 'image/png', 'image/gif']
11}));
12
13// Handle file uploads
14app.post('/upload', (req, res) => {
15  const files = req.files;
16  
17  if (!files || Object.keys(files).length === 0) {
18    return res.status(400).json({ error: 'No files uploaded' });
19  }
20
21  return {
22    success: true,
23    files: Object.values(files).map((f: any) => ({
24      filename: f.filename,
25      size: f.size,
26      mimetype: f.mimetype
27    }))
28  };
29});

Why File Upload Handling Matters

Without proper file upload handling, you're vulnerable to security issues, missing validation, and no processing capabilities. With MoroJS, you get all of that automatically.

Traditional file upload setup requires manual validation and security. We handle that automatically.

Without Proper Handling

  • Security vulnerabilities
  • No file type validation
  • No size limits
  • No malware scanning

With MoroJS

  • Built-in security features
  • Automatic file type validation
  • Configurable size limits
  • Optional malware scanning

Why It Makes Sense

Secure

Built-in validation, malware scanning, and security features.

Flexible

Local storage, cloud storage, and processing pipelines.

Configurable

Size limits, file types, and processing options.

How It Works

MoroJS includes a built-in upload middleware that handles file uploads with automatic validation, type checking, and security features. The middleware provides a simple API for handling both single and multiple file uploads, with support for file size limits, type restrictions, and custom storage options.

Basic File Upload

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

Built-in File Upload Middleware

typescript

1import { createApp, builtInMiddleware } from '@morojs/moro';
2
3const app = createApp({
4  cors: true,
5  compression: true
6});
7
8// Built-in upload middleware with validation
9app.use(builtInMiddleware.upload({
10  dest: './uploads',
11  maxFileSize: 10 * 1024 * 1024, // 10MB
12  maxFiles: 5,
13  allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']
14}));
15
16// Single file upload endpoint
17app.post('/upload', (req, res) => {
18  const files = req.files;
19  
20  if (!files || Object.keys(files).length === 0) {
21    return res.status(400).json({ 
22      success: false, 
23      error: 'No file uploaded' 
24    });
25  }
26  
27  const file = Object.values(files)[0];
28  
29  return {
30    success: true,
31    file: {
32      filename: file.filename,
33      originalname: file.originalname,
34      mimetype: file.mimetype,
35      size: file.size,
36      path: file.path
37    }
38  };
39});
40
41// Multiple file upload endpoint
42app.post('/upload/multiple', (req, res) => {
43  const files = req.files;
44  
45  if (!files || Object.keys(files).length === 0) {
46    return res.status(400).json({ 
47      success: false, 
48      error: 'No files uploaded' 
49    });
50  }
51  
52  const fileList = Object.values(files).map((file: any) => ({
53    filename: file.filename,
54    originalname: file.originalname,
55    mimetype: file.mimetype,
56    size: file.size,
57    path: file.path
58  }));
59  
60  return {
61    success: true,
62    files: fileList,
63    count: fileList.length
64  };
65});

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

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});

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});

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