Skip to content

Commit d78f807

Browse files
committed
Update README
1 parent 15ff40b commit d78f807

File tree

1 file changed

+75
-19
lines changed

1 file changed

+75
-19
lines changed

packages/form-data-parser/README.md

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@ A streaming `multipart/form-data` parser that solves memory issues with file upl
1212

1313
## Why You Need This
1414

15-
The native [`request.formData()` method](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) has a major flaw in server environments: it buffers all file uploads in memory. When your users upload large files, this can quickly exhaust your server's RAM and crash your application.
15+
The native [`request.formData()` method](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) has a few major flaws in server environments:
1616

17-
`form-data-parser` solves this by handling file uploads as they arrive in the request body stream, allowing the user to safely put the file in storage, and use some other value (like a unique identifier for that file) in the returned `FormData` object.
17+
- It buffers all file uploads in memory
18+
- It does not provide fine-grained control over file upload handling
19+
- It does not prevent DoS attacks from malicious requests
20+
21+
In normal usage, this makes it difficult to process requests with large file uploads because they can exhaust your server's RAM and crash the application.
22+
23+
For attackers, this creates an attack vector where malicious actors can overwhelm your server's memory by sending large payloads with many files.
24+
25+
`form-data-parser` solves this by handling file uploads as they arrive in the request body stream, allowing you to safely store files and use either a) the `File` directly or b) a unique identifier for that file in the returned `FormData` object.
1826

1927
## Installation
2028

@@ -26,7 +34,71 @@ npm install @mjackson/form-data-parser
2634

2735
## Usage
2836

29-
This library pairs really well with [the `file-storage` library](https://github.com/mjackson/remix-the-web/tree/main/packages/file-storage) for keeping files in various storage backends.
37+
The `parseFormData` interface allows you to define an "upload handler" function for fine-grained control of handling file uploads.
38+
39+
```ts
40+
import * as fsp from 'node:fs/promises';
41+
import type { FileUpload } from '@mjackson/form-data-parser';
42+
import { parseFormData } from '@mjackson/form-data-parser';
43+
44+
// Define how to handle incoming file uploads
45+
async function uploadHandler(fileUpload: FileUpload) {
46+
// Is this file upload from the <input type="file" name="user-avatar"> field?
47+
if (fileUpload.fieldName === 'user-avatar') {
48+
let filename = `/uploads/user-${user.id}-avatar.bin`;
49+
50+
// Store the file safely on disk
51+
await fsp.writeFile(filename, fileUpload.bytes);
52+
53+
// Return the file name to use in the FormData object so we don't
54+
// keep the file contents around in memory.
55+
return filename;
56+
}
57+
58+
// Ignore unrecognized fields
59+
}
60+
61+
// Handle form submissions with file uploads
62+
async function requestHandler(request: Request) {
63+
// Parse the form data from the request.body stream, passing any files
64+
// through your upload handler as they are parsed from the stream
65+
let formData = await parseFormData(request, uploadHandler);
66+
67+
let avatarFilename = formData.get('user-avatar');
68+
69+
if (avatarFilename != null) {
70+
console.log(`User avatar uploaded to ${avatarFilename}`);
71+
} else {
72+
console.log(`No user avatar file was uploaded`);
73+
}
74+
}
75+
```
76+
77+
To limit the maximum size of files that are uploaded, or the maximum number of files that may be uploaded in a single request, use the `maxFileSize` and `maxFiles` options.
78+
79+
```ts
80+
import { MaxFilesExceededError, MaxFileSizeExceededError } from '@mjackson/form-data-parser';
81+
82+
const oneKb = 1024;
83+
const oneMb = 1024 * oneKb;
84+
85+
try {
86+
let formData = await parseFormData(request, {
87+
maxFiles: 5,
88+
maxFileSize: 10 * oneMb,
89+
});
90+
} catch (error) {
91+
if (error instanceof MaxFilesExceededError) {
92+
console.error(`Request may not contain more than 5 files`);
93+
} else if (error instanceof MaxFileSizeExceededError) {
94+
console.error(`Files may not be larger than 10 MiB`);
95+
} else {
96+
console.error(`An unknown error occurred:`, error);
97+
}
98+
}
99+
```
100+
101+
If you're looking for a more flexible storage solution for `File` objects that are uploaded, this library pairs really well with [the `file-storage` library](https://github.com/mjackson/remix-the-web/tree/main/packages/file-storage) for keeping files in various storage backends.
30102

31103
```ts
32104
import { LocalFileStorage } from '@mjackson/file-storage/local';
@@ -47,26 +119,10 @@ async function uploadHandler(fileUpload: FileUpload) {
47119

48120
// Return a lazy File object that can access the stored file when needed
49121
return fileStorage.get(storageKey);
50-
51-
// Note: You could also just return the `storageKey` here if
52-
// that's the value you want to show up in the `FormData` object
53-
// at the "user-avatar" key.
54122
}
55123

56124
// Ignore unrecognized fields
57125
}
58-
59-
// Handle form submissions with file uploads
60-
async function requestHandler(request: Request) {
61-
// Parse the form data, streaming any files through your upload handler
62-
let formData = await parseFormData(request, uploadHandler);
63-
64-
// Access uploaded files just like with native FormData
65-
let file = formData.get('user-avatar'); // File object
66-
file.name; // "my-avatar.jpg" (original filename)
67-
file.size; // File size in bytes
68-
file.type; // "image/jpeg" (MIME type)
69-
}
70126
```
71127

72128
## Related Packages

0 commit comments

Comments
 (0)