In this blog post, you'll discover Formidable, a light and efficient package for form and file uploading handling.
Project setup
We need to create a new project, so
npm init -y
Then install Formidable:
pnpm add formidable
Note: don't forget to setup the project for ESM (add "type": "module",
into your package.json
).
Now let's create our app!
Upload
Create a new index.js
file:
import http from 'node:http'
import formidable, {errors as formidableErrors} from 'formidable'
const FORM_TEMPLATE = `
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="text" name="filename" />
<input type="file" name="files" multiple="multiple" />
<input type="submit" value="Upload" />
</form>
`
createServer((req, res) => {
if (req.url === '/upload' && req.method.toLowerCase() === 'post'){
const form = formidable({
// we'll put our formidable config later
})
let fields
let files
try {
[fields, files] = await form.parse(req)
} catch (err) {
console.error(err)
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' })
res.end(err.message)
return
}
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ fields, files }))
return
}
// show the form
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(FORM_TEMPLATE)
})
.listen(3000)
We are going to configure our Formidable instance.
const form = formidable({
uploadDir: 'uploads',
keepExtensions: true,
filename: (name, ext, part, form) => {
// don't forget to import `randomBytes` from `crypto` module
return `${name}.${randomBytes(8).toString('base64url')}${ext}`
}
})
We are putting a random file id that will permit users to upload files with the same name without overriding them.
Error handling
In the catch
, you can decide to put a custom error message on a specific error.
// ...
} catch (err) {
console.error(err)
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' })
if (err.code === formidableErrors.maxFieldsExceeded) {
res.end("max fields exceeded")
return
}
res.end(err.message)
return
}
// ....
View file
Add this line on the top of your server handling function:
const url = new URL(req.url, `http://${req.headers.host}`)
This will serve to parse our url path, params, etc...
Now, after our if (req.url === "/upload" ...)
, put this condition:
if (url.pathname === "/file") {
const filepath = url.searchParams.get("path")
const fileContent = await readFile(`./uploads/${filepath}`)
res.writeHead(200, { 'Content-Type': "text/plain" })
res.end(fileContent)
return
}
You need to import readFile
from fs/promises
.
But we have a little problem: if we have an image, for example, the Content-Type
of the response won't match if the current response's body format. To avoid this we need to have a new function that will search for the format of a file depending on its extension.
Response & file format matching
Create a new file named content-types.json
:
{
"jar": "application/java-archive",
"ogg": "application/ogg",
"pdf": "application/pdf",
"xhtml": "application/xhtml+xml",
"json": "application/json",
"app/xml": "application/xml",
"zip": "application/zip",
"gif": "image/gif",
"jpeg": "image/jpeg",
"png": "image/png",
"tiff": "image/tiff",
"svg": "image/svg+xml",
"multipart/mixed": "multipart/mixed",
"multipart/alternative": "multipart/alternative",
"multipart/related": "multipart/related (using by MHTML (HTML mail).)",
"multipart/form-data": "multipart/form-data",
"css": "text/css",
"csv": "text/csv",
"html": "text/html",
"js": "text/javascript",
"txt": "text/plain",
"xml": "text/xml",
"mpeg": "video/mpeg",
"mp4": "video/mp4"
}
These are some file formats, if you want, add others...
Then, create a new function called getFileFormat
in your index.js
file:
async function getFileFormat(filename) {
const supportedContentTypes = JSON.parse(await readFile("./content-types.json", "utf8"))
const ext = filename.split('.')[filename.split('.').length-1]
return supportedContentTypes[ext] ?? "text/plain"
}
This function will get the content of content-types.json
and will get the associated format. If it's undefined it will return the default text/plain
.
Then you need to edit your http Content-Type
header:
if (url.pathname === "/file") {
// ...
res.writeHead(200, { 'Content-Type': await getFileFormat(filepath) })
// ...
}
Testing
Now you can test your application!
To get the upload form, visit /
, that will return you to /upload
that tell you some informations about the POST request (where the current file is saved). Then you can get the file name and go to /file?path=<the file name>
to get the file in your browser.
Top comments (0)