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