0
# Storage Engines
1
2
Storage engines determine where and how uploaded files are stored. Multer provides built-in disk and memory storage engines, with support for custom storage implementations.
3
4
## Capabilities
5
6
### Disk Storage
7
8
Stores uploaded files on the local file system with configurable destination and filename handling.
9
10
```javascript { .api }
11
/**
12
* Creates a disk storage engine for saving files to the file system
13
* @param options - Configuration options for disk storage
14
* @returns DiskStorage instance
15
*/
16
const diskStorage = multer.diskStorage(options: DiskStorageOptions);
17
18
interface DiskStorageOptions {
19
/** Directory or function to determine where files are stored */
20
destination?: string | DestinationFunction;
21
/** Function to determine the filename for stored files */
22
filename?: FilenameFunction;
23
}
24
25
type DestinationFunction = (
26
req: Request,
27
file: File,
28
cb: (error: Error | null, destination: string) => void
29
) => void;
30
31
type FilenameFunction = (
32
req: Request,
33
file: File,
34
cb: (error: Error | null, filename: string) => void
35
) => void;
36
```
37
38
**Usage Examples:**
39
40
```javascript
41
const multer = require('multer');
42
const path = require('path');
43
44
// Simple disk storage with static destination
45
const storage = multer.diskStorage({
46
destination: './uploads',
47
filename: (req, file, cb) => {
48
// Generate unique filename with timestamp
49
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
50
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
51
}
52
});
53
54
// Dynamic destination based on file type
55
const dynamicStorage = multer.diskStorage({
56
destination: (req, file, cb) => {
57
let folder = 'uploads/';
58
if (file.mimetype.startsWith('image/')) {
59
folder += 'images/';
60
} else if (file.mimetype === 'application/pdf') {
61
folder += 'documents/';
62
} else {
63
folder += 'others/';
64
}
65
cb(null, folder);
66
},
67
filename: (req, file, cb) => {
68
// Keep original filename with timestamp prefix
69
cb(null, Date.now() + '-' + file.originalname);
70
}
71
});
72
73
// User-specific uploads
74
const userStorage = multer.diskStorage({
75
destination: (req, file, cb) => {
76
const userId = req.user ? req.user.id : 'anonymous';
77
cb(null, `uploads/users/${userId}/`);
78
},
79
filename: (req, file, cb) => {
80
// Sanitize filename and add timestamp
81
const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
82
cb(null, Date.now() + '-' + sanitized);
83
}
84
});
85
86
const upload = multer({ storage: storage });
87
```
88
89
### Memory Storage
90
91
Stores uploaded files in memory as Buffer objects, useful for temporary processing or cloud storage uploads.
92
93
```javascript { .api }
94
/**
95
* Creates a memory storage engine for keeping files in memory
96
* @returns MemoryStorage instance
97
*/
98
const memoryStorage = multer.memoryStorage();
99
```
100
101
**Usage Examples:**
102
103
```javascript
104
const multer = require('multer');
105
const storage = multer.memoryStorage();
106
const upload = multer({ storage: storage });
107
108
app.post('/process-image', upload.single('image'), (req, res) => {
109
if (req.file) {
110
// File is available as Buffer in req.file.buffer
111
console.log('File size:', req.file.buffer.length);
112
console.log('File type:', req.file.mimetype);
113
114
// Process the buffer (resize, compress, etc.)
115
processImageBuffer(req.file.buffer)
116
.then(processedBuffer => {
117
// Upload to cloud storage, save to database, etc.
118
return uploadToCloud(processedBuffer, req.file.originalname);
119
})
120
.then(cloudUrl => {
121
res.json({ success: true, url: cloudUrl });
122
})
123
.catch(error => {
124
res.status(500).json({ error: error.message });
125
});
126
} else {
127
res.status(400).json({ error: 'No file uploaded' });
128
}
129
});
130
131
// Batch processing multiple files in memory
132
app.post('/batch-process', upload.array('files', 10), (req, res) => {
133
if (req.files && req.files.length > 0) {
134
const processPromises = req.files.map(file => {
135
return processFileBuffer(file.buffer, file.mimetype);
136
});
137
138
Promise.all(processPromises)
139
.then(results => {
140
res.json({ processed: results.length, results });
141
})
142
.catch(error => {
143
res.status(500).json({ error: error.message });
144
});
145
}
146
});
147
```
148
149
### File Objects by Storage Type
150
151
Different storage engines add different properties to the file object:
152
153
```javascript { .api }
154
// DiskStorage file object
155
interface DiskStorageFile extends File {
156
/** Directory where the file was saved */
157
destination: string;
158
/** Name of the file within the destination directory */
159
filename: string;
160
/** Full path to the saved file */
161
path: string;
162
/** Size of the file in bytes */
163
size: number;
164
}
165
166
// MemoryStorage file object
167
interface MemoryStorageFile extends File {
168
/** Buffer containing the entire file */
169
buffer: Buffer;
170
/** Size of the file in bytes */
171
size: number;
172
}
173
174
// Base file properties (available in both storage types)
175
interface File {
176
/** Field name from the form */
177
fieldname: string;
178
/** Original filename from user's computer */
179
originalname: string;
180
/** File encoding type */
181
encoding: string;
182
/** MIME type of the file */
183
mimetype: string;
184
}
185
```
186
187
### Custom Storage Engines
188
189
You can create custom storage engines by implementing the required interface:
190
191
```javascript { .api }
192
/**
193
* Custom storage engine interface
194
* Must implement _handleFile and _removeFile methods
195
*/
196
interface StorageEngine {
197
_handleFile(req: Request, file: File, cb: StorageCallback): void;
198
_removeFile(req: Request, file: File, cb: RemoveCallback): void;
199
}
200
201
type StorageCallback = (error: Error | null, info?: any) => void;
202
type RemoveCallback = (error: Error | null) => void;
203
```
204
205
**Custom Storage Example:**
206
207
```javascript
208
// Example: Custom storage that saves to a database
209
function DatabaseStorage(options) {
210
this.database = options.database;
211
}
212
213
DatabaseStorage.prototype._handleFile = function(req, file, cb) {
214
const chunks = [];
215
216
file.stream.on('data', chunk => chunks.push(chunk));
217
file.stream.on('error', cb);
218
file.stream.on('end', () => {
219
const buffer = Buffer.concat(chunks);
220
221
// Save to database
222
this.database.saveFile({
223
filename: file.originalname,
224
mimetype: file.mimetype,
225
data: buffer,
226
size: buffer.length
227
})
228
.then(result => {
229
cb(null, {
230
fileId: result.id,
231
size: buffer.length,
232
filename: file.originalname
233
});
234
})
235
.catch(cb);
236
});
237
};
238
239
DatabaseStorage.prototype._removeFile = function(req, file, cb) {
240
this.database.deleteFile(file.fileId)
241
.then(() => cb(null))
242
.catch(cb);
243
};
244
245
// Usage
246
const databaseStorage = new DatabaseStorage({ database: myDb });
247
const upload = multer({ storage: databaseStorage });
248
```
249
250
## Storage Configuration Best Practices
251
252
### Security Considerations
253
254
```javascript
255
// Secure filename generation
256
const secureFilename = (req, file, cb) => {
257
// Remove potentially dangerous characters
258
const sanitized = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
259
260
// Add timestamp to prevent conflicts
261
const timestamp = Date.now();
262
263
// Limit filename length
264
const maxLength = 100;
265
const truncated = sanitized.length > maxLength
266
? sanitized.substring(0, maxLength)
267
: sanitized;
268
269
cb(null, `${timestamp}-${truncated}`);
270
};
271
272
// Secure destination handling
273
const secureDestination = (req, file, cb) => {
274
// Prevent directory traversal
275
const userId = req.user?.id?.replace(/[^a-zA-Z0-9]/g, '') || 'anonymous';
276
const safeDestination = path.join('uploads', userId);
277
278
// Ensure directory exists
279
fs.makedirs(safeDestination, { recursive: true }, (err) => {
280
if (err) return cb(err);
281
cb(null, safeDestination);
282
});
283
};
284
```
285
286
### Performance Considerations
287
288
```javascript
289
// For high-volume uploads, use disk storage
290
const highVolumeStorage = multer.diskStorage({
291
destination: './uploads',
292
filename: (req, file, cb) => {
293
// Use crypto for truly unique filenames
294
const crypto = require('crypto');
295
const hash = crypto.randomBytes(16).toString('hex');
296
cb(null, hash + path.extname(file.originalname));
297
}
298
});
299
300
// For temporary processing, use memory storage with limits
301
const tempProcessingUpload = multer({
302
storage: multer.memoryStorage(),
303
limits: {
304
fileSize: 10 * 1024 * 1024, // 10MB limit
305
files: 1 // Single file only
306
}
307
});
308
```