Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

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.

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

Handle file uploads with Multer

typescript

1import { createApp } from '@morojs/moro';
2import multer from 'multer';
3
4const app = createApp();
5
6const upload = multer({
7  storage: multer.diskStorage({
8    destination: './uploads/',
9    filename: (req, file, cb) => {
10      const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
11      cb(null, file.fieldname + '-' + uniqueSuffix);
12    }
13  }),
14  limits: { fileSize: 10 * 1024 * 1024 } // 10MB
15});
16
17app.post('/upload', upload.single('file'), (req, res) => {
18  return {
19    success: true,
20    file: {
21      filename: req.file.filename,
22      originalname: req.file.originalname,
23      size: req.file.size
24    }
25  };
26});

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 provides built-in support for file uploads with validation, type checking, and security features. You can use middleware libraries like Multer for advanced features, or configure MoroJS's built-in file upload handling with cloud storage adapters and processing pipelines.

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

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