Logo

dev-resources.site

for different kinds of informations.

How to upload files to a server in NodeJS with Formidable

Published at
12/7/2024
Categories
node
fileupload
formidable
vanilla
Author
plleonart
Author
9 person written this
plleonart
open
How to upload files to a server in NodeJS with Formidable

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
Enter fullscreen mode Exit fullscreen mode

Then install Formidable:

pnpm add formidable
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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}`
  }
})
Enter fullscreen mode Exit fullscreen mode

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
    }

    // ....
Enter fullscreen mode Exit fullscreen mode

View file

Add this line on the top of your server handling function:

const url = new URL(req.url, `http://${req.headers.host}`)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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

  // ...
}
Enter fullscreen mode Exit fullscreen mode

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.

vanilla Article's
30 articles in total
Favicon
🎨🛠️ 𝗩𝗮𝗻𝗶𝗹𝗹𝗮 𝗙𝗿𝗮𝗺𝗲𝘄𝗼𝗿𝗸 𝘁𝗼 𝗘𝗺𝗽𝗼𝘄𝗲𝗿 𝗗𝗲𝘃𝗲𝗹𝗼𝗽𝗲𝗿𝘀 🚀🌐
Favicon
Mastering Vanilla JavaScript and Libraries: The Road to Dynamic DOM Rendering
Favicon
🌟 Vanilla Update: New Components and Enhanced Features! 🌟
Favicon
🌐 Unlock Development with Vanilla: The Non-Framework Powerhouse 🌐
Favicon
🚀 Vanilla & CSSer Major Update! 🚀
Favicon
How to upload files to a server in NodeJS with Formidable
Favicon
🚀 Vanilla Update: A New Development Methodology! 🚀
Favicon
Secure Text Encryption and Decryption with Vanilla JavaScript
Favicon
🚀 Vanilla Framework Update: Meet CSSer! 🚀
Favicon
🌟 Vanilla & CSSer Accessibility Update! 🌟
Favicon
Storing and retrieving JavaScript objects in localStorage
Favicon
New lightbox package here!
Favicon
Data-driven UIs
Favicon
A11y: Vanilla javascript aria-live announcer
Favicon
Keyboard input in Node.js
Favicon
Javascript Made Simple!
Favicon
Array methods and when to use them, forEach, map, reduce
Favicon
Which should you use? Array.from vs. Spread Operator
Favicon
Tiny Vanilla.js Projects
Favicon
Maneras de clonar un objecto en javascript
Favicon
Vanilla JavaScript data fetch
Favicon
Vanilla(ts) configuration with Webpack. a little bit different.
Favicon
My first Chrome Extension
Favicon
Speedy Typer Game
Favicon
What is CSS @scope and why should I care?
Favicon
Path aliases with Vanilla Node JS
Favicon
Montar SPA de cero con Vanilla y Jest
Favicon
Web Apps from scratch: Forms
Favicon
Sending asynchronous POST requests with pure JS: ditching jQuery's ajax
Favicon
State Management with Vanilla JavaScript

Featured ones: