0
# File System Integration
1
2
Pluggable file system abstraction that allows custom implementations for different storage backends, cloud storage, or specialized file handling requirements.
3
4
## Capabilities
5
6
### Methods Override Object
7
8
Custom file system method implementations that override the default Node.js fs operations.
9
10
```javascript { .api }
11
interface Methods {
12
/** Custom lstat implementation for getting file/directory stats */
13
lstat?: (path: string, fromDir?: boolean) => Promise<Stats>;
14
/** Custom realpath implementation for resolving symbolic links */
15
realpath?: (path: string) => Promise<string>;
16
/** Custom read stream creator for file content streaming */
17
createReadStream?: (path: string, options?: object) => ReadableStream;
18
/** Custom directory reader for listing directory contents */
19
readdir?: (path: string) => Promise<string[]>;
20
/** Custom error handler for generating error responses */
21
sendError?: (absolutePath: string, response: ServerResponse, acceptsJSON: boolean, current: string, handlers: object, config: Config, spec: ErrorSpec) => Promise<void>;
22
}
23
```
24
25
### File Statistics
26
27
Custom file stat implementation for getting file and directory information.
28
29
```javascript { .api }
30
/**
31
* Custom lstat implementation for getting file/directory stats
32
* @param path - Absolute path to the file or directory
33
* @param fromDir - Whether the call originated from directory listing (optional)
34
* @returns Promise resolving to file stats object
35
*/
36
lstat?: (path: string, fromDir?: boolean) => Promise<Stats>;
37
38
interface Stats {
39
/** File size in bytes */
40
size: number;
41
/** Last modified time */
42
mtime: Date;
43
/** Whether this is a directory */
44
isDirectory(): boolean;
45
/** Whether this is a symbolic link */
46
isSymbolicLink(): boolean;
47
}
48
```
49
50
**Usage Examples:**
51
52
```javascript
53
const handler = require('serve-handler');
54
55
// Custom lstat for cloud storage
56
const methods = {
57
lstat: async (path, fromDir) => {
58
const cloudFile = await cloudStorage.stat(path);
59
return {
60
size: cloudFile.size,
61
mtime: new Date(cloudFile.lastModified),
62
isDirectory: () => cloudFile.type === 'directory',
63
isSymbolicLink: () => false
64
};
65
}
66
};
67
68
await handler(req, res, {}, methods);
69
```
70
71
### Symbolic Link Resolution
72
73
Custom implementation for resolving symbolic links to their target paths.
74
75
```javascript { .api }
76
/**
77
* Custom realpath implementation for resolving symbolic links
78
* @param path - Path to resolve
79
* @returns Promise resolving to the real path
80
*/
81
realpath?: (path: string) => Promise<string>;
82
```
83
84
**Usage Examples:**
85
86
```javascript
87
const methods = {
88
realpath: async (path) => {
89
// Custom symlink resolution logic
90
return await customResolveSymlink(path);
91
}
92
};
93
```
94
95
### File Content Streaming
96
97
Custom read stream implementation for streaming file content to clients.
98
99
```javascript { .api }
100
/**
101
* Custom read stream creator for file content streaming
102
* @param path - Absolute path to the file
103
* @param options - Stream options including start/end for range requests
104
* @returns Readable stream of file content
105
*/
106
createReadStream?: (path: string, options?: StreamOptions) => ReadableStream;
107
108
interface StreamOptions {
109
/** Start byte position for range requests */
110
start?: number;
111
/** End byte position for range requests */
112
end?: number;
113
}
114
```
115
116
**Usage Examples:**
117
118
```javascript
119
const methods = {
120
createReadStream: (path, options = {}) => {
121
// Custom stream implementation (e.g., from database or cloud storage)
122
return cloudStorage.createReadStream(path, {
123
start: options.start,
124
end: options.end
125
});
126
}
127
};
128
129
// Example with S3
130
const AWS = require('aws-sdk');
131
const s3 = new AWS.S3();
132
133
const methods = {
134
createReadStream: (path, options = {}) => {
135
const params = {
136
Bucket: 'my-bucket',
137
Key: path.replace('/public/', '')
138
};
139
140
if (options.start !== undefined || options.end !== undefined) {
141
params.Range = `bytes=${options.start || 0}-${options.end || ''}`;
142
}
143
144
return s3.getObject(params).createReadStream();
145
}
146
};
147
```
148
149
### Directory Listing
150
151
Custom directory reading implementation for listing directory contents.
152
153
```javascript { .api }
154
/**
155
* Custom directory reader for listing directory contents
156
* @param path - Absolute path to the directory
157
* @returns Promise resolving to array of file/directory names
158
*/
159
readdir?: (path: string) => Promise<string[]>;
160
```
161
162
**Usage Examples:**
163
164
```javascript
165
const methods = {
166
readdir: async (path) => {
167
// Custom directory listing (e.g., from database)
168
const files = await database.listFiles(path);
169
return files.map(f => f.name);
170
}
171
};
172
173
// Example with cloud storage
174
const methods = {
175
readdir: async (path) => {
176
const objects = await cloudStorage.listObjects({
177
prefix: path.replace('/public/', ''),
178
delimiter: '/'
179
});
180
181
return objects.map(obj => obj.name);
182
}
183
};
184
```
185
186
### Error Handling
187
188
Custom error response handler for generating custom error pages and responses.
189
190
```javascript { .api }
191
/**
192
* Custom error handler for generating error responses
193
* @param absolutePath - Absolute path that caused the error
194
* @param response - HTTP response object
195
* @param acceptsJSON - Whether client accepts JSON responses
196
* @param current - Current working directory
197
* @param handlers - Handler utilities object
198
* @param config - Configuration object
199
* @param spec - Error specification object
200
* @returns Promise that resolves when error response is sent
201
*/
202
sendError?: (
203
absolutePath: string,
204
response: ServerResponse,
205
acceptsJSON: boolean,
206
current: string,
207
handlers: object,
208
config: Config,
209
spec: ErrorSpec
210
) => Promise<void>;
211
212
interface ErrorSpec {
213
/** HTTP status code */
214
statusCode: number;
215
/** Error code identifier */
216
code: string;
217
/** Error message */
218
message: string;
219
/** Original error object (optional) */
220
err?: Error;
221
}
222
```
223
224
**Usage Examples:**
225
226
```javascript
227
const methods = {
228
sendError: async (absolutePath, response, acceptsJSON, current, handlers, config, spec) => {
229
// Custom error handling
230
if (spec.statusCode === 404) {
231
response.statusCode = 404;
232
response.setHeader('Content-Type', 'text/html');
233
response.end(`
234
<html>
235
<body>
236
<h1>Custom 404 Page</h1>
237
<p>The file ${absolutePath} was not found.</p>
238
</body>
239
</html>
240
`);
241
} else {
242
// Log error to external service
243
await errorLogger.log({
244
path: absolutePath,
245
error: spec.err,
246
statusCode: spec.statusCode
247
});
248
249
// Use default error handling
250
await handlers.sendError(absolutePath, response, acceptsJSON, current, handlers, config, spec);
251
}
252
}
253
};
254
```
255
256
### Complete Custom Implementation Example
257
258
```javascript
259
const handler = require('serve-handler');
260
const AWS = require('aws-sdk');
261
const redis = require('redis');
262
263
const s3 = new AWS.S3();
264
const redisClient = redis.createClient();
265
266
// Complete custom file system implementation for S3 + Redis caching
267
const methods = {
268
lstat: async (path, fromDir) => {
269
// Check cache first
270
const cached = await redisClient.get(`stat:${path}`);
271
if (cached) {
272
return JSON.parse(cached);
273
}
274
275
// Get from S3
276
try {
277
const object = await s3.headObject({
278
Bucket: 'my-static-site',
279
Key: path.replace('/public/', '')
280
}).promise();
281
282
const stats = {
283
size: object.ContentLength,
284
mtime: object.LastModified,
285
isDirectory: () => false,
286
isSymbolicLink: () => false
287
};
288
289
// Cache for 5 minutes
290
await redisClient.setex(`stat:${path}`, 300, JSON.stringify(stats));
291
return stats;
292
} catch (err) {
293
if (err.code === 'NotFound') {
294
// Check if it's a "directory" (prefix)
295
const objects = await s3.listObjectsV2({
296
Bucket: 'my-static-site',
297
Prefix: path.replace('/public/', '') + '/',
298
MaxKeys: 1
299
}).promise();
300
301
if (objects.Contents.length > 0) {
302
return {
303
size: 0,
304
mtime: new Date(),
305
isDirectory: () => true,
306
isSymbolicLink: () => false
307
};
308
}
309
}
310
throw err;
311
}
312
},
313
314
createReadStream: (path, options = {}) => {
315
const params = {
316
Bucket: 'my-static-site',
317
Key: path.replace('/public/', '')
318
};
319
320
if (options.start !== undefined || options.end !== undefined) {
321
params.Range = `bytes=${options.start || 0}-${options.end || ''}`;
322
}
323
324
return s3.getObject(params).createReadStream();
325
},
326
327
readdir: async (path) => {
328
const objects = await s3.listObjectsV2({
329
Bucket: 'my-static-site',
330
Prefix: path.replace('/public/', '') + '/',
331
Delimiter: '/'
332
}).promise();
333
334
const files = [];
335
336
// Add files in this directory
337
objects.Contents.forEach(obj => {
338
const name = obj.Key.split('/').pop();
339
if (name) files.push(name);
340
});
341
342
// Add subdirectories
343
objects.CommonPrefixes.forEach(prefix => {
344
const name = prefix.Prefix.split('/').slice(-2)[0];
345
if (name) files.push(name);
346
});
347
348
return files;
349
},
350
351
sendError: async (absolutePath, response, acceptsJSON, current, handlers, config, spec) => {
352
// Log to CloudWatch
353
const cloudwatch = new AWS.CloudWatchLogs();
354
await cloudwatch.putLogEvents({
355
logGroupName: '/aws/lambda/static-site',
356
logStreamName: 'errors',
357
logEvents: [{
358
timestamp: Date.now(),
359
message: JSON.stringify({
360
path: absolutePath,
361
statusCode: spec.statusCode,
362
message: spec.message
363
})
364
}]
365
}).promise();
366
367
// Use default error handling
368
await handlers.sendError(absolutePath, response, acceptsJSON, current, handlers, config, spec);
369
}
370
};
371
372
// Use with custom methods
373
await handler(request, response, { public: '/public' }, methods);
374
```